diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:33 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:33 +0000 |
commit | 086c044dc34dfc0f74fbe41f4ecb402b2cd34884 (patch) | |
tree | a4f824bd33cb075dd5aa3eb5a0a94af221bbe83a /browser/components/search/test | |
parent | Adding debian version 124.0.1-1. (diff) | |
download | firefox-086c044dc34dfc0f74fbe41f4ecb402b2cd34884.tar.xz firefox-086c044dc34dfc0f74fbe41f4ecb402b2cd34884.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/search/test')
49 files changed, 2870 insertions, 331 deletions
diff --git a/browser/components/search/test/browser/browser_426329.js b/browser/components/search/test/browser/browser_426329.js index 093c793048..c793f6c27e 100644 --- a/browser/components/search/test/browser/browser_426329.js +++ b/browser/components/search/test/browser/browser_426329.js @@ -292,7 +292,7 @@ add_task(async function testClearHistory() { function promiseObserver(topic) { return new Promise(resolve => { - let obs = (aSubject, aTopic, aData) => { + let obs = (aSubject, aTopic) => { Services.obs.removeObserver(obs, aTopic); resolve(aSubject); }; diff --git a/browser/components/search/test/browser/browser_contentSearch.js b/browser/components/search/test/browser/browser_contentSearch.js index 7b9328fb94..07753927f1 100644 --- a/browser/components/search/test/browser/browser_contentSearch.js +++ b/browser/components/search/test/browser/browser_contentSearch.js @@ -50,6 +50,14 @@ add_setup(async function () { await SearchTestUtils.promiseNewSearchEngine({ url: getRootDirectory(gTestPath) + "testEngine_chromeicon.xml", }); + + // Install a WebExtension based engine to allow testing passing of plain + // URIs (moz-extension://) to the content process. + await SearchTestUtils.installSearchExtension({ + icons: { + 16: "favicon.ico", + }, + }); }); add_task(async function GetState() { @@ -460,7 +468,7 @@ var currentStateObj = async function (isPrivateWindowValue, hiddenEngine = "") { ), }; for (let engine of await Services.search.getVisibleEngines()) { - let uri = engine.getIconURL(16); + let uri = await engine.getIconURL(16); state.engines.push({ name: engine.name, iconData: await iconDataFromURI(uri), @@ -476,7 +484,7 @@ var currentStateObj = async function (isPrivateWindowValue, hiddenEngine = "") { }; async function constructEngineObj(engine) { - let uriFavicon = engine.getIconURL(16); + let uriFavicon = await engine.getIconURL(16); return { name: engine.name, iconData: await iconDataFromURI(uriFavicon), @@ -491,7 +499,7 @@ function iconDataFromURI(uri) { ); } - if (!uri.startsWith("data:")) { + if (!uri.startsWith("data:") && !uri.startsWith("blob:")) { plainURIIconTested = true; return Promise.resolve(uri); } diff --git a/browser/components/search/test/browser/browser_contentSearchUI.js b/browser/components/search/test/browser/browser_contentSearchUI.js index 9196b1355c..da6044f35f 100644 --- a/browser/components/search/test/browser/browser_contentSearchUI.js +++ b/browser/components/search/test/browser/browser_contentSearchUI.js @@ -24,17 +24,6 @@ ChromeUtils.defineESModuleGetters(this, { "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() { @@ -261,6 +250,19 @@ 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; @@ -290,8 +292,20 @@ add_setup(async function () { } 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(); @@ -1096,10 +1110,6 @@ add_task(async function settings() { await msg("reset"); }); -add_task(async function cleanup() { - Services.search.restoreDefaultEngines(); -}); - function checkState( actualState, expectedInputVal, @@ -1147,10 +1157,10 @@ function checkState( } var gMsgMan; - +var tabs = []; async function promiseTab() { let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); - registerCleanupFunction(() => BrowserTestUtils.removeTab(tab)); + tabs.push(tab); let loadedPromise = BrowserTestUtils.firstBrowserLoaded(window); openTrustedLinkIn("about:test-about-content-search-ui", "current"); diff --git a/browser/components/search/test/browser/browser_contentSearchUI_default.js b/browser/components/search/test/browser/browser_contentSearchUI_default.js index 47114fa6da..5410cfc826 100644 --- a/browser/components/search/test/browser/browser_contentSearchUI_default.js +++ b/browser/components/search/test/browser/browser_contentSearchUI_default.js @@ -58,11 +58,22 @@ async function ensureIcon(tab, expectedIcon) { "Search Icon not set." ); - Assert.equal( - computedStyle.getPropertyValue("--newtab-search-icon"), - `url(${icon})`, - "Should have the expected icon" - ); + if (icon.startsWith("blob:")) { + // We don't check the data here as `browser_contentSearch.js` performs + // those checks. + Assert.ok( + computedStyle + .getPropertyValue("--newtab-search-icon") + .startsWith("url(blob:"), + "Should have a blob URL" + ); + } else { + Assert.equal( + computedStyle.getPropertyValue("--newtab-search-icon"), + `url(${icon})`, + "Should have the expected icon" + ); + } } ); } @@ -96,7 +107,7 @@ async function runNewTabTest(isHandoff) { waitForLoad: false, }); - let engineIcon = defaultEngine.getIconURL(16); + let engineIcon = await defaultEngine.getIconURL(16); await ensureIcon(tab, engineIcon); if (isHandoff) { @@ -162,7 +173,7 @@ add_task(async function test_content_search_attributes_in_private_window() { }); let tab = win.gBrowser.selectedTab; - let engineIcon = defaultEngine.getIconURL(16); + let engineIcon = await defaultEngine.getIconURL(16); await ensureIcon(tab, engineIcon); await ensurePlaceholder( diff --git a/browser/components/search/test/browser/browser_defaultPrivate_nimbus.js b/browser/components/search/test/browser/browser_defaultPrivate_nimbus.js index ce5acc91a0..41b9ce7576 100644 --- a/browser/components/search/test/browser/browser_defaultPrivate_nimbus.js +++ b/browser/components/search/test/browser/browser_defaultPrivate_nimbus.js @@ -31,6 +31,47 @@ const CONFIG_DEFAULT = [ }, ]; +const CONFIG_V2 = [ + { + recordType: "engine", + identifier: "basic", + base: { + name: "basic", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + recordType: "engine", + identifier: "private", + base: { + name: "private", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + }, + }, + variants: [{ environment: { allRegionsAndLocales: true } }], + }, + { + recordType: "defaultEngines", + globalDefault: "basic", + globalDefaultPrivate: "private", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + SearchTestUtils.init(this); add_setup(async () => { @@ -50,7 +91,9 @@ add_setup(async () => { }); SearchTestUtils.useMockIdleService(); - await SearchTestUtils.updateRemoteSettingsConfig(CONFIG_DEFAULT); + await SearchTestUtils.updateRemoteSettingsConfig( + SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG_DEFAULT + ); registerCleanupFunction(async () => { let settingsWritten = SearchTestUtils.promiseSearchNotification( diff --git a/browser/components/search/test/browser/browser_google_behavior.js b/browser/components/search/test/browser/browser_google_behavior.js index cce3b3ce1f..ccc84e8bba 100644 --- a/browser/components/search/test/browser/browser_google_behavior.js +++ b/browser/components/search/test/browser/browser_google_behavior.js @@ -55,7 +55,7 @@ if (code) { } function promiseContentSearchReady(browser) { - return SpecialPowers.spawn(browser, [], async function (args) { + return SpecialPowers.spawn(browser, [], async function () { return new Promise(resolve => { SpecialPowers.pushPrefEnv({ set: [ @@ -175,7 +175,7 @@ async function testSearchEngine(engineDetails) { await promiseContentSearchReady(browser); }, async run(tab) { - await SpecialPowers.spawn(tab.linkedBrowser, [], async function (args) { + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { let input = content.document.querySelector("input[id*=search-]"); input.focus(); input.value = "foo"; diff --git a/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js b/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js index 9f05e948ed..174a86ba3f 100644 --- a/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js +++ b/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js @@ -71,10 +71,10 @@ async function testSearchBarChangeEngine(win, testPrivate, isPrivateWindow) { if (testPrivate == isPrivateWindow) { let expectedName = originalEngine.name; - let expectedImage = originalEngine.getIconURL(); + let expectedImage = await originalEngine.getIconURL(); if (isPrivateWindow) { expectedName = originalPrivateEngine.name; - expectedImage = originalPrivateEngine.getIconURL(); + expectedImage = await originalPrivateEngine.getIconURL(); } Assert.equal( diff --git a/browser/components/search/test/browser/browser_rich_suggestions.js b/browser/components/search/test/browser/browser_rich_suggestions.js index 98adedcee5..3daefc12d9 100644 --- a/browser/components/search/test/browser/browser_rich_suggestions.js +++ b/browser/components/search/test/browser/browser_rich_suggestions.js @@ -17,6 +17,58 @@ const CONFIG_DEFAULT = [ }, ]; +const CONFIG_V2 = [ + { + recordType: "engine", + identifier: "basic", + base: { + name: "basic", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + trending: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + params: [ + { + name: "richsuggestions", + value: "true", + }, + ], + }, + suggestions: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + params: [ + { + name: "richsuggestions", + value: "true", + }, + ], + searchTermParamName: "query", + }, + }, + aliases: ["basic"], + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "defaultEngines", + globalDefault: "basic", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + SearchTestUtils.init(this); add_setup(async () => { @@ -37,7 +89,9 @@ add_setup(async () => { }); SearchTestUtils.useMockIdleService(); - await SearchTestUtils.updateRemoteSettingsConfig(CONFIG_DEFAULT); + await SearchTestUtils.updateRemoteSettingsConfig( + SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG_DEFAULT + ); registerCleanupFunction(async () => { let settingsWritten = SearchTestUtils.promiseSearchNotification( diff --git a/browser/components/search/test/browser/browser_searchEngine_behaviors.js b/browser/components/search/test/browser/browser_searchEngine_behaviors.js index 15a30583bf..295e069857 100644 --- a/browser/components/search/test/browser/browser_searchEngine_behaviors.js +++ b/browser/components/search/test/browser/browser_searchEngine_behaviors.js @@ -22,9 +22,13 @@ const SEARCH_ENGINE_DETAILS = [ }, { alias: "b", - baseURL: `https://www.bing.com/search?{code}pc=${ - SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "MOZR" : "MOZI" - }&q=foo`, + baseURL: SearchUtils.newSearchConfigEnabled + ? `https://www.bing.com/search?pc=${ + SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "MOZR" : "MOZI" + }&{code}q=foo` + : `https://www.bing.com/search?{code}pc=${ + SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "MOZR" : "MOZI" + }&q=foo`, codes: { context: "form=MOZCON&", keyword: "form=MOZLBR&", @@ -74,7 +78,7 @@ const SEARCH_ENGINE_DETAILS = [ ]; function promiseContentSearchReady(browser) { - return SpecialPowers.spawn(browser, [], async function (args) { + return SpecialPowers.spawn(browser, [], async function () { SpecialPowers.pushPrefEnv({ set: [ [ diff --git a/browser/components/search/test/browser/browser_searchbar_keyboard_navigation.js b/browser/components/search/test/browser/browser_searchbar_keyboard_navigation.js index ee292db1b5..7e2be41993 100644 --- a/browser/components/search/test/browser/browser_searchbar_keyboard_navigation.js +++ b/browser/components/search/test/browser/browser_searchbar_keyboard_navigation.js @@ -29,18 +29,10 @@ async function checkHeader(engine) { // The header can be updated after getting the engine, so we may have to // wait for it. let header = searchPopup.searchbarEngineName; - if (!header.getAttribute("value").includes(engine.name)) { - await new Promise(resolve => { - let observer = new MutationObserver(() => { - observer.disconnect(); - resolve(); - }); - observer.observe(searchPopup.searchbarEngineName, { - attributes: true, - attributeFilter: ["value"], - }); - }); - } + await TestUtils.waitForCondition( + () => header.getAttribute("value").includes(engine.name), + "Should have the correct engine name displayed in the header" + ); Assert.ok( header.getAttribute("value").includes(engine.name), "Should have the correct engine name displayed in the header" diff --git a/browser/components/search/test/browser/browser_searchbar_openpopup.js b/browser/components/search/test/browser/browser_searchbar_openpopup.js index 2653e65e8d..32c6995f69 100644 --- a/browser/components/search/test/browser/browser_searchbar_openpopup.js +++ b/browser/components/search/test/browser/browser_searchbar_openpopup.js @@ -126,7 +126,7 @@ add_task(async function open_empty() { let image = searchPopup.querySelector(".searchbar-engine-image"); Assert.equal( image.src, - engine.getIconURL(16), + await engine.getIconURL(16), "Should have the correct icon" ); @@ -267,6 +267,13 @@ add_no_popup_task(async function right_click_doesnt_open_popup() { context_click(textbox); let contextPopup = await promise; + // Assert that the context menu click inside the popup does nothing. If it + // opens something, assert_no_popup_task will make us fail. On macOS this + // doesn't work because of native context menus. + if (!navigator.platform.includes("Mac")) { + context_click(contextPopup); + } + is( Services.focus.focusedElement, textbox, diff --git a/browser/components/search/test/browser/browser_trending_suggestions.js b/browser/components/search/test/browser/browser_trending_suggestions.js index 74d0b944d5..efe54d2da5 100644 --- a/browser/components/search/test/browser/browser_trending_suggestions.js +++ b/browser/components/search/test/browser/browser_trending_suggestions.js @@ -22,6 +22,65 @@ const CONFIG_DEFAULT = [ }, ]; +const CONFIG_V2 = [ + { + recordType: "engine", + identifier: "basic", + base: { + name: "basic", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + trending: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + }, + }, + aliases: ["basic"], + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "engine", + identifier: "private", + base: { + name: "private", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + suggestions: { + base: "https://example.com", + method: "GET", + searchTermParamName: "search", + }, + }, + aliases: ["private"], + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "defaultEngines", + globalDefault: "basic", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + SearchTestUtils.init(this); add_setup(async () => { @@ -38,7 +97,9 @@ add_setup(async () => { }); SearchTestUtils.useMockIdleService(); - await SearchTestUtils.updateRemoteSettingsConfig(CONFIG_DEFAULT); + await SearchTestUtils.updateRemoteSettingsConfig( + SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG_DEFAULT + ); Services.telemetry.clearScalars(); registerCleanupFunction(async () => { diff --git a/browser/components/search/test/browser/contentSearchUI.html b/browser/components/search/test/browser/contentSearchUI.html index 09abe822b2..7fa41b9d86 100644 --- a/browser/components/search/test/browser/contentSearchUI.html +++ b/browser/components/search/test/browser/contentSearchUI.html @@ -13,6 +13,9 @@ </head> <body> +<!-- Dummy Button is used to ensure pressing Shift+Tab on <input> will make the new focus + - remains in the same document, rather than the Chrome UI. --> +<button>Dummy Button</button> <div id="container"><input type="text" value=""/></div> <script src="chrome://mochitests/content/browser/browser/components/search/test/browser/contentSearchUI.js"> diff --git a/browser/components/search/test/browser/head.js b/browser/components/search/test/browser/head.js index 7a45a9f4f5..6051ef1caa 100644 --- a/browser/components/search/test/browser/head.js +++ b/browser/components/search/test/browser/head.js @@ -123,7 +123,7 @@ async function searchInSearchbar(inputText, win = window) { return sb.textbox.popup; } -function clearSearchbarHistory(win = window) { +function clearSearchbarHistory() { info("cleanup the search history"); return FormHistory.update({ op: "remove", fieldname: "searchbar-history" }); } diff --git a/browser/components/search/test/browser/telemetry/browser.toml b/browser/components/search/test/browser/telemetry/browser.toml index 49d8f256aa..660fc4eae2 100644 --- a/browser/components/search/test/browser/telemetry/browser.toml +++ b/browser/components/search/test/browser/telemetry/browser.toml @@ -4,10 +4,7 @@ support-files = ["head.js", "head-spa.js"] prefs = ["browser.search.log=true"] ["browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js"] -support-files = [ - "domain_category_mappings.json", - "searchTelemetryDomainCategorizationReporting.html", -] +support-files = ["searchTelemetryDomainCategorizationReporting.html"] ["browser_search_glean_serp_event_telemetry_enabled_by_nimbus_variable.js"] support-files = ["searchTelemetryAd.html"] @@ -31,9 +28,16 @@ support-files = [ "searchTelemetryAd_components_carousel_outer_container.html", "searchTelemetryAd_components_text.html", "searchTelemetryAd_components_visibility.html", + "searchTelemetryAd_components_cookie_banner.html", "serp.css", ] +["browser_search_telemetry_adImpression_component_skipCount_children.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + +["browser_search_telemetry_adImpression_component_skipCount_parent.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + ["browser_search_telemetry_categorization_timing.js"] ["browser_search_telemetry_content.js"] @@ -42,7 +46,6 @@ support-files = [ support-files = ["searchTelemetryDomainCategorizationReporting.html"] ["browser_search_telemetry_domain_categorization_download_timer.js"] -support-files = ["domain_category_mappings.json"] ["browser_search_telemetry_domain_categorization_extraction.js"] support-files = ["searchTelemetryDomainExtraction.html"] @@ -85,6 +88,15 @@ support-files = [ "serp.css", ] +["browser_search_telemetry_engagement_eventListeners_children.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + +["browser_search_telemetry_engagement_eventListeners_parent.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + +["browser_search_telemetry_engagement_ignoreLinkRegexps.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + ["browser_search_telemetry_engagement_multiple_tabs.js"] support-files = [ "searchTelemetryAd_searchbox_with_content.html", @@ -98,6 +110,14 @@ support-files = [ "serp.css", ] +["browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js"] +support-files = [ + "searchTelemetryAd_searchbox_with_redirecting_links.html", + "searchTelemetryAd_shopping.html", + "searchTelemetry_redirect_with_js.html", + "serp.css", +] + ["browser_search_telemetry_engagement_query_params.js"] support-files = [ "searchTelemetryAd_components_query_parameters.html", diff --git a/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js b/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js index ed71a7c5ed..e73a9601d4 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js +++ b/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js @@ -38,7 +38,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js index 8049406d40..5a09353ed6 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js @@ -78,6 +78,15 @@ const TEST_PROVIDER_INFO = [ type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, default: true, }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + included: { + parent: { + selector: "#banner", + }, + }, + topDown: true, + }, ], }, ]; @@ -500,3 +509,35 @@ add_task(async function test_impressions_without_ads() { BrowserTestUtils.removeTab(tab); }); + +add_task(async function test_ad_impressions_with_cookie_banner() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + await waitForPageWithAdImpressions(); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_children.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_children.js new file mode 100644 index 0000000000..65cd612a49 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_children.js @@ -0,0 +1,149 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests skipCount property on elements in the children. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + let components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + TEST_PROVIDER_INFO[0].components = components; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +// For older clients, skipCount won't be available. +add_task(async function test_skip_count_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + children: [ + { + selector: "a", + }, + ], + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_false() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + children: [ + { + selector: "a", + skipCount: false, + }, + ], + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_true() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + children: [ + { + selector: "a", + skipCount: true, + }, + ], + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [], + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_parent.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_parent.js new file mode 100644 index 0000000000..8471215840 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_parent.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests skipCount property on parent elements. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + let components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + TEST_PROVIDER_INFO[0].components = components; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +// For older clients, skipCount won't be available. +add_task(async function test_skip_count_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_false() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: false, + }, + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_true() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [], + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js index ce18f64e9f..246caf6f47 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js @@ -30,7 +30,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js index d01141d826..b8dd85da97 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js @@ -33,7 +33,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, @@ -119,6 +119,7 @@ add_task(async function test_download_after_failure() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); @@ -173,6 +174,7 @@ add_task(async function test_download_after_multiple_failures() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); @@ -220,6 +222,7 @@ add_task(async function test_cancel_download_timer() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); @@ -277,6 +280,7 @@ add_task(async function test_download_adjust() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js index 03ddb75481..e653be6c48 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js @@ -11,6 +11,10 @@ ChromeUtils.defineESModuleGetters(this, { SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", }); +// The search provider's name is provided to ensure we can extract domains +// from relative links, e.g. /url?=https://www.foobar.com +const SEARCH_PROVIDER_NAME = "example"; + const TESTS = [ { title: "Extract domain from href (absolute URL) - one link.", @@ -35,7 +39,7 @@ const TESTS = [ expectedDomains: ["foo.com", "bar.com", "baz.com", "qux.com"], }, { - title: "Extract domain from href (relative URL).", + title: "Extract domain from href (relative URL / URL matching provider)", extractorInfos: [ { selectors: @@ -43,38 +47,33 @@ const TESTS = [ method: "href", }, ], - expectedDomains: ["example.org"], + expectedDomains: [], }, { title: "Extract domain from data attribute - one link.", extractorInfos: [ { selectors: "#test4 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, }, ], - expectedDomains: ["www.abc.com"], + expectedDomains: ["abc.com"], }, { title: "Extract domain from data attribute - multiple links.", extractorInfos: [ { selectors: "#test5 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, }, ], - expectedDomains: [ - "www.foo.com", - "www.bar.com", - "www.baz.com", - "www.qux.com", - ], + expectedDomains: ["foo.com", "bar.com", "baz.com", "qux.com"], }, { title: "Extract domain from an href's query param value.", @@ -88,7 +87,7 @@ const TESTS = [ }, }, ], - expectedDomains: ["def.com"], + expectedDomains: ["def.com", "bar.com", "baz.com"], }, { title: @@ -144,7 +143,7 @@ const TESTS = [ }, { selectors: "#test10 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, @@ -158,7 +157,7 @@ const TESTS = [ }, }, ], - expectedDomains: ["foobar.com", "www.abc.com", "def.com"], + expectedDomains: ["foobar.com", "abc.com", "def.com"], }, { title: "No elements match the selectors.", @@ -176,7 +175,7 @@ const TESTS = [ extractorInfos: [ { selectors: "#test12 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, @@ -208,6 +207,161 @@ const TESTS = [ ], expectedDomains: [], }, + { + title: "Second-level domains to a top-level domain.", + extractorInfos: [ + { + selectors: "#test15 a", + method: "href", + }, + ], + expectedDomains: [ + "foobar.gc.ca", + "foobar.gov.uk", + "foobar.co.uk", + "foobar.co.il", + ], + }, + { + title: "URL with a long subdomain.", + extractorInfos: [ + { + selectors: "#test16 a", + method: "href", + }, + ], + expectedDomains: ["foobar.com"], + }, + { + title: "URLs with the same top level domain.", + extractorInfos: [ + { + selectors: "#test17 a", + method: "href", + }, + ], + expectedDomains: ["foobar.com"], + }, + { + title: "Maximum domains extracted from a single selector.", + extractorInfos: [ + { + selectors: "#test18 a", + method: "href", + }, + ], + expectedDomains: [ + "foobar1.com", + "foobar2.com", + "foobar3.com", + "foobar4.com", + "foobar5.com", + "foobar6.com", + "foobar7.com", + "foobar8.com", + "foobar9.com", + "foobar10.com", + ], + }, + { + // This is just in case we use multiple selectors meant for separate SERPs + // and the provider switches to re-using their markup. + title: "Maximum domains extracted from multiple matching selectors.", + extractorInfos: [ + { + selectors: "#test19 a.foo", + method: "href", + }, + { + selectors: "#test19 a.baz", + method: "href", + }, + ], + expectedDomains: [ + "foobar1.com", + "foobar2.com", + "foobar3.com", + "foobar4.com", + "foobar5.com", + "foobar6.com", + "foobar7.com", + "foobar8.com", + "foobar9.com", + // This is from the second selector. + "foobaz1.com", + ], + }, + { + title: "Bing organic result.", + extractorInfos: [ + { + selectors: "#test20 #b_results .b_algo .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: ["organic.com"], + }, + { + title: "Bing sponsored result.", + extractorInfos: [ + { + selectors: "#test21 #b_results .b_ad .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: ["sponsored.com"], + }, + { + title: "Bing carousel result.", + extractorInfos: [ + { + selectors: "#test22 .adsMvCarousel cite", + method: "textContent", + }, + ], + expectedDomains: ["fixedupfromthecarousel.com"], + }, + { + title: "Bing sidebar result.", + extractorInfos: [ + { + selectors: "#test23 aside cite", + method: "textContent", + }, + ], + expectedDomains: ["fixedupfromthesidebar.com"], + }, + { + title: "Extraction threshold respected using text content method.", + extractorInfos: [ + { + selectors: "#test24 #b_results .b_ad .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: [ + "sponsored1.com", + "sponsored2.com", + "sponsored3.com", + "sponsored4.com", + "sponsored5.com", + "sponsored6.com", + "sponsored7.com", + "sponsored8.com", + "sponsored9.com", + "sponsored10.com", + ], + }, + { + title: "Bing organic result with no protocol.", + extractorInfos: [ + { + selectors: "#test25 #b_results .b_algo .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: ["organic.com"], + }, ]; add_setup(async function () { @@ -240,14 +394,15 @@ add_task(async function test_domain_extraction_heuristics() { let expectedDomains = new Set(currentTest.expectedDomains); let actualDomains = await SpecialPowers.spawn( gBrowser.selectedBrowser, - [currentTest.extractorInfos], - extractorInfos => { + [currentTest.extractorInfos, SEARCH_PROVIDER_NAME], + (extractorInfos, searchProviderName) => { const { domainExtractor } = ChromeUtils.importESModule( "resource:///actors/SearchSERPTelemetryChild.sys.mjs" ); return domainExtractor.extractDomainsFromDocument( content.document, - extractorInfos + extractorInfos, + searchProviderName ); } ); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js index f328bb4f79..4c47b0b14a 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js @@ -30,7 +30,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js index b7edb8763f..973f17b760 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js @@ -30,7 +30,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js index cfb8590960..9d3ac2c931 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js @@ -31,7 +31,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js index cb95164221..c73e224eae 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js @@ -32,7 +32,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js index a7ea62ebd5..f94e6b0bd8 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js @@ -138,8 +138,8 @@ add_task(async function test_click_tab() { { impression: { provider: "example", - tagged: "false", - partner_code: "", + tagged: "true", + partner_code: "ff", source: "unknown", is_shopping_page: "false", is_private: "false", @@ -217,8 +217,8 @@ add_task(async function test_click_shopping() { { impression: { provider: "example", - tagged: "false", - partner_code: "", + tagged: "true", + partner_code: "ff", source: "unknown", is_shopping_page: "true", is_private: "false", diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_children.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_children.js new file mode 100644 index 0000000000..4f5aaf9378 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_children.js @@ -0,0 +1,480 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests eventListeners property on children elements in topDown searches. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SELECTOR = ".arrow"; +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +add_task(async function test_listeners_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_no_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_listener() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [{ eventType: "click" }], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: ".arrow-next", + tab, + }); + await synthesizePageAction({ + selector: ".arrow-prev", + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +/** + * The click event is by far our most used event so by default, we translate + * a "click" eventType to a "clicked" action. If no action is provided for + * another type of event, nothing should be reported. + */ +add_task(async function test_event_with_no_default_action() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [{ eventType: "mousedown" }], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_event_no_default_action_with_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "mousedown", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [{ eventType: "click", target: "custom_target" }], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_and_action_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + action: "custom_action", + target: "custom_target", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "custom_action", + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_multiple_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + }, + { + eventType: "mouseover", + action: "mouseovered", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + event: { + type: "mouseover", + }, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: "mouseovered", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: ".arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "keydownEnter", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + let pageActionPromise = waitForPageWithAction(); + await EventUtils.synthesizeKey("KEY_Enter"); + await pageActionPromise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "keydowned", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition_invalid() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: ".arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "noConditionExistsWithThisName", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + await EventUtils.synthesizeKey("KEY_Enter"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_parent.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_parent.js new file mode 100644 index 0000000000..4e3c635b4c --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_parent.js @@ -0,0 +1,434 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests eventListeners property on parent elements in topDown searches. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +// The impression doesn't change in these tests. +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SELECTOR = ".arrow"; +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +add_task(async function test_listeners_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_no_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_listener() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: ".arrow-next", + tab, + }); + await synthesizePageAction({ + selector: ".arrow-prev", + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +/** + * The click event is by far our most used event so by default, we translate + * a "click" eventType to a "clicked" action. If no action is provided for + * another type of event, nothing should be reported. + */ +add_task(async function test_event_with_no_default_action_parent() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "mousedown", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_event_no_default_action_with_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "mousedown", + action: "clicked", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + target: "custom_target", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_and_action_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + target: "custom_target", + action: "custom_action", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "custom_action", + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_multiple_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + action: "clicked", + }, + { + eventType: "mouseover", + action: "mouseovered", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + await synthesizePageAction({ + selector: SELECTOR, + tab, + event: { + type: "mouseover", + }, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: "mouseovered", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "keydownEnter", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + let pageActionPromise = waitForPageWithAction(); + await EventUtils.synthesizeKey("KEY_Enter"); + await pageActionPromise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "keydowned", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition_invalid() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "noConditionExistsWithThisName", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + await EventUtils.synthesizeKey("KEY_Enter"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_ignoreLinkRegexps.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_ignoreLinkRegexps.js new file mode 100644 index 0000000000..10f2a2d836 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_ignoreLinkRegexps.js @@ -0,0 +1,223 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests ignoreLinkRegexps property in search telemetry that explicitly results + * in our network code ignoring the link. The main reason for doing so is for + * rare situations where we need to find a components from a topDown approach + * but it loads a page in the network process. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + ignoreLinkRegexps: [ + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_searchbox_with_content.html\?s=test&page=images/, + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_searchbox_with_content.html\?s=test&page=shopping/, + ], + components: [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ], + hrefToComponentMapAugmentation: [ + { + action: "clicked_something", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + url: "https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox.html", + }, + ], + }, +]; + +// The impression doesn't change in these tests. +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +add_task(async function test_click_link_1_matching_ignore_link_regexps() { + resetTelemetry(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#images", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + abandonment: { + reason: SearchSERPTelemetryUtils.ABANDONMENTS.NAVIGATION, + }, + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_link_2_matching_ignore_link_regexps() { + resetTelemetry(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#shopping", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + abandonment: { + reason: SearchSERPTelemetryUtils.ABANDONMENTS.NAVIGATION, + }, + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_link_3_not_matching_ignore_link_regexps() { + resetTelemetry(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#extra", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.NON_ADS_LINK, + }, + ], + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_listener_with_ignore_link_regexps() { + resetTelemetry(); + + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + topDown: true, + included: { + parent: { + selector: "nav a", + skipCount: true, + eventListeners: [ + { + eventType: "click", + action: "clicked", + }, + ], + }, + }, + }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#images", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js new file mode 100644 index 0000000000..93a6b7993e --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js @@ -0,0 +1,252 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * These tests load SERPs and click on links. + */ + +"use strict"; + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetry/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + nonAdsLinkRegexps: [ + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetry_redirect_with_js/, + ], + nonAdsLinkQueryParamNames: ["url"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + shoppingTab: { + regexp: "&page=shopping", + selector: "nav a", + inspectRegexpInSERP: true, + }, + components: [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ], + }, +]; + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + resetTelemetry(); + }); +}); + +add_task(async function test_click_absolute_url_in_query_param() { + resetTelemetry(); + + let url = getSERPUrl( + "searchTelemetryAd_searchbox_with_redirecting_links.html" + ); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#shopping-redirect-absolute-link", + {}, + tab.linkedBrowser + ); + await browserLoadedPromise; + await waitForPageWithAdImpressions(); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "true", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "true", + is_private: "false", + shopping_tab_displayed: "true", + }, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); + +add_task(async function test_click_relative_href_in_query_param() { + resetTelemetry(); + + let url = getSERPUrl( + "searchTelemetryAd_searchbox_with_redirecting_links.html" + ); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#shopping-redirect-relative-link", + {}, + tab.linkedBrowser + ); + await browserLoadedPromise; + await waitForPageWithAdImpressions(); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "true", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "true", + is_private: "false", + shopping_tab_displayed: "true", + }, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); + +add_task(async function test_click_irrelevant_href_in_query_param() { + resetTelemetry(); + + let url = getSERPUrl( + "searchTelemetryAd_searchbox_with_redirecting_links.html" + ); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true, + "https://example.org/foo/bar" + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#organic-redirect", + {}, + tab.linkedBrowser + ); + await browserLoadedPromise; + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "true", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.NON_ADS_LINK, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js index b30a7bc0c1..8f7f7f4e05 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js @@ -22,6 +22,7 @@ const TEST_PROVIDER_INFO = [ /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_nonAdsLink_redirect.html/, ], extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + ignoreLinkRegexps: [/^https:\/\/example\.org\/consent\?data=/], components: [ { type: SearchSERPTelemetryUtils.COMPONENTS.AD_CAROUSEL, @@ -90,6 +91,44 @@ const TEST_PROVIDER_INFO = [ type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, default: true, }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + topDown: true, + included: { + parent: { + selector: "#banner", + }, + children: [ + { + selector: "#cookie_accept", + eventListeners: [ + { + eventType: "click", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_ACCEPT, + }, + ], + }, + { + selector: "#cookie_reject", + eventListeners: [ + { + eventType: "click", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_REJECT, + }, + ], + }, + { + selector: "#cookie_more_options", + eventListeners: [ + { + eventType: "click", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_MORE_OPTIONS, + }, + ], + }, + ], + }, + }, ], }, ]; @@ -455,3 +494,138 @@ add_task(async function test_click_link_with_special_characters_in_path() { SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); await waitForIdle(); }); + +// Test that clicking the accept button on the cookie banner is correctly +// tracked as an engagement event. +add_task(async function test_click_cookie_banner_accept() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#cookie_accept", + {}, + tab.linkedBrowser + ); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_ACCEPT, + target: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Test that clicking the reject button on the cookie banner is accurately +// recorded as an engagement event. +add_task(async function test_click_cookie_banner_reject() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#cookie_reject", + {}, + tab.linkedBrowser + ); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_REJECT, + target: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Test that clicking the more options button on the cookie banner is accurately +// recorded as an engagement event. +add_task(async function test_click_cookie_banner_more_options() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#cookie_more_options", + {}, + tab.linkedBrowser + ); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_MORE_OPTIONS, + target: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js index f7b22f004b..cb9e123622 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js @@ -152,7 +152,7 @@ async function track_ad_click( add_task(async function test_source_webextension_search() { /* global browser */ - async function background(SEARCH_TERM) { + async function background() { // Search with no tabId browser.search.search({ query: "searchSuggestion", engine: "Example" }); } @@ -184,7 +184,7 @@ add_task(async function test_source_webextension_search() { }); add_task(async function test_source_webextension_query() { - async function background(SEARCH_TERM) { + async function background() { // Search with no tabId browser.search.query({ text: "searchSuggestion", diff --git a/browser/components/search/test/browser/telemetry/domain_category_mappings.json b/browser/components/search/test/browser/telemetry/domain_category_mappings.json deleted file mode 100644 index 2f8d0d2af2..0000000000 --- a/browser/components/search/test/browser/telemetry/domain_category_mappings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "DqNorjpE3CBY9OZh0wf1uA==": [2, 90], - "kpuib0kvhtSp1moICEmGWg==": [2, 95], - "+5WbbjV3Nmxp0mBZODcJWg==": [2, 78, 4, 10], - "OIHlWZ/yMyTHHuY78AV9VQ==": [3, 90], - "r1hDZinn+oNrQjabn8IB9w==": [4, 90], - "AtlIam7nqWvzFzTGkYI01w==": [4, 90] -} diff --git a/browser/components/search/test/browser/telemetry/head.js b/browser/components/search/test/browser/telemetry/head.js index 416451e400..b798099bdd 100644 --- a/browser/components/search/test/browser/telemetry/head.js +++ b/browser/components/search/test/browser/telemetry/head.js @@ -45,6 +45,10 @@ ChromeUtils.defineLazyGetter(this, "SEARCH_AD_CLICK_SCALARS", () => { ]; }); +ChromeUtils.defineLazyGetter(this, "gCryptoHash", () => { + return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); +}); + // For use with categorization. const APP_MAJOR_VERSION = parseInt(Services.appinfo.version).toString(); const CHANNEL = SearchUtils.MODIFIED_APP_CHANNEL; @@ -207,6 +211,11 @@ function resetTelemetry() { * values we use to validate the recorded Glean impression events. */ function assertSERPTelemetry(expectedEvents) { + // Do a deep copy of impressions in case the input is using constants, as + // we insert impression id into the expected events to make it easier to + // run Assert.deepEqual() on the expected and actual result. + expectedEvents = JSON.parse(JSON.stringify(expectedEvents)); + // A single test might run assertImpressionEvents more than once // so the Set needs to be cleared or else the impression event // check will throw. @@ -385,6 +394,46 @@ add_setup(function () { }); }); +async function openSerpInNewTab(url, expectedAds = true) { + let promise; + if (expectedAds) { + promise = waitForPageWithAdImpressions(); + } + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await promise; + + let cleanup = async () => { + await BrowserTestUtils.removeTab(tab); + resetTelemetry(); + }; + + return { tab, cleanup }; +} + +async function synthesizePageAction({ + selector, + event = {}, + tab, + expectEngagement = true, +} = {}) { + let promise; + if (expectEngagement) { + promise = waitForPageWithAction(); + } else { + // Wait roughly around how much it might take for a possible page action + // to be registered in telemetry. + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + promise = new Promise(resolve => setTimeout(resolve, 50)); + } + await BrowserTestUtils.synthesizeMouseAtCenter( + selector, + event, + tab.linkedBrowser + ); + + await promise; +} + function assertCategorizationValues(expectedResults) { // TODO Bug 1868476: Replace with calls to Glean telemetry. let actualResults = [...fakeTelemetryStorage]; @@ -435,6 +484,10 @@ function assertCategorizationValues(expectedResults) { } } +function waitForPageWithAction() { + return TestUtils.topicObserved("reported-page-with-action"); +} + function waitForPageWithAdImpressions() { return TestUtils.topicObserved("reported-page-with-ad-impressions"); } @@ -459,10 +512,9 @@ registerCleanupFunction(async () => { await PlacesUtils.history.clear(); }); -async function mockRecordWithAttachment({ id, version, filename }) { +async function mockRecordWithAttachment({ id, version, filename, mapping }) { // Get the bytes of the file for the hash and size for attachment metadata. - let data = await IOUtils.readUTF8(getTestFilePath(filename)); - let buffer = new TextEncoder().encode(data).buffer; + let buffer = new TextEncoder().encode(JSON.stringify(mapping)).buffer; let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance( Ci.nsIArrayBufferInputStream ); @@ -506,6 +558,30 @@ async function resetCategorizationCollection(record) { await client.db.importChanges({}, Date.now()); } +const MOCK_ATTACHMENT_VALUES = { + "abc.com": [2, 95], + "abc.org": [4, 90], + "def.com": [2, 78, 4, 10], + "def.org": [4, 90], + "foobar.org": [3, 90], +}; + +const CONVERTED_ATTACHMENT_VALUES = convertDomainsToHashes( + MOCK_ATTACHMENT_VALUES +); + +function convertDomainsToHashes(domainsToCategories) { + let newObj = {}; + for (let [key, value] of Object.entries(domainsToCategories)) { + gCryptoHash.init(gCryptoHash.SHA256); + let bytes = new TextEncoder().encode(key); + gCryptoHash.update(bytes, key.length); + let hash = gCryptoHash.finish(true); + newObj[hash] = value; + } + return newObj; +} + async function insertRecordIntoCollection() { const client = RemoteSettings(TELEMETRY_CATEGORIZATION_KEY); const db = client.db; @@ -515,6 +591,7 @@ async function insertRecordIntoCollection() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await client.attachments.cacheImpl.set(record.id, attachment); diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryAd_components_cookie_banner.html b/browser/components/search/test/browser/telemetry/searchTelemetryAd_components_cookie_banner.html new file mode 100644 index 0000000000..e33afb2672 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/searchTelemetryAd_components_cookie_banner.html @@ -0,0 +1,16 @@ +<html> +<head> + <title>A top-level page with cookie banner</title> +</head> +<body> + <h1>This is the top-level page</h1> + <div id="banner"> + <button id="cookie_accept">Accept</button> + <button id="cookie_reject">Reject</button> + <button id="cookie_more_options" + onclick="location.href='https:example.org/consent?data='"> + More Options + </button> + </div> +</body> +</html> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html index 9c4d371691..d23255984f 100644 --- a/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html +++ b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html @@ -11,13 +11,16 @@ </form> </section> <nav> - <a id="images" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=images">Images</a> - <a id="shopping" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=shopping">Shopping</a> - <a id="extra" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox.html?s=test">Extra Page</a> + <a id="images" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=images&abc=ff">Images</a> + <a id="shopping" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=shopping&abc=ff">Shopping</a> + <a id="extra" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox.html?s=test&abc=ff">Extra Page</a> </nav> <section class="refined-search-buttons"> + <button class="arrow arrow-prev">← Prev</button> <a id="refined-search-button" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test's">Test's</a> <a id="refined-search-button-with-partner-code" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test2&abc=ff">Test 2</a> + <a href="javascript:void(0)">Element relying on Javascript</a> + <button class="arrow arrow-next">Next →</button> </section> <section id="searchresults"> <div class="lhs"> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_redirecting_links.html b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_redirecting_links.html new file mode 100644 index 0000000000..2b09409126 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_redirecting_links.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" href="./serp.css" /> +</head> +<body> + <nav> + <a href="/">All Results</a> + <a id="shopping-redirect-relative-link" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_shopping.html?s=test&page=shopping&abc=ff">Shopping Relative</a> + <a id="shopping-redirect-absolute-link" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_shopping.html?s=test&page=shopping&abc=ff">Shopping Absolute</a> + </nav> + <section> + <form role="search"> + <input type="text" value="test" /> + </form> + </section> + <section id="searchresults"> + <a id="organic-redirect" href="https://example.org/foo/bar">Organic Redirect Result</a> + </section> +</body> +<script type="text/javascript"> + const ORIGIN = "https://example.org"; + const PATH = "/browser/browser/components/search/test/browser/telemetry/" + const REDIRECT_URL = `${ORIGIN + PATH}searchTelemetry_redirect_with_js.html`; + const SHOPPING_PAGE = "searchTelemetryAd_shopping.html?s=test&page=shopping&abc=ff"; + document.getElementById("shopping-redirect-relative-link").addEventListener("click", event => { + event.preventDefault(); + window.location.href = `${REDIRECT_URL}?url=${encodeURIComponent(PATH + SHOPPING_PAGE)}`; + }); + document.getElementById("shopping-redirect-absolute-link").addEventListener("click", event => { + event.preventDefault(); + window.location.href = `${REDIRECT_URL}?url=${encodeURIComponent(ORIGIN + PATH + SHOPPING_PAGE)}`; + }); + document.getElementById("organic-redirect").addEventListener("click", event => { + event.preventDefault(); + window.location.href = `${REDIRECT_URL}?url=${encodeURIComponent("https://example.org/foo/bar")}`; + }); +</script> +</html> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html b/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html index b49e5610ae..28c31af959 100644 --- a/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html +++ b/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html @@ -25,6 +25,8 @@ <div id="test3"> <div data-layout="organic"> <a href="/dummy-page" data-testid="result-title-a">Extract domain from href (relative URL).</a> + <a href="https://example.org/dummy-page" data-testid="result-title-a">Extract domain from href.</a> + <a href="https://www.example.org/dummy-page" data-testid="result-title-a">Extract domain from href.</a> </div> </div> @@ -40,7 +42,9 @@ </div> <div id="test6"> - <a href="example.com/testing?ad_domain=def.com" class="js-carousel-item-title">Extract domain from an href's query param value.</a> + <a href="https://www.example.org/testing?ad_domain=def.com" class="js-carousel-item-title">Extract domain from an href's query param value.</a> + <a href="https://example.org/testing?ad_domain=bar.com" class="js-carousel-item-title">Extract domain from an href's query param value.</a> + <a href="/testing?ad_domain=baz.com" class="js-carousel-item-title">Extract domain from a relative href containing a relevant query param value.</a> </div> <div id="test7"> @@ -79,6 +83,179 @@ <div id="test14"> <a href="git://testing.com/testrepo">Non-standard URL scheme.</a> </div> + + <div id="test15"> + <h5>Second-level domains to a top-level domain.</h5> + <a href="https://www.foobar.gc.ca/">Link</a> + <a href="https://www.foobar.gov.uk/">Link</a> + <a href="https://foobar.co.uk">Link</a> + <a href="https://www.foobar.co.il">Link</a> + </div> + + <div id="test16"> + <a href="https://ab.cd.ef.gh.foobar.com/">URL with a long subdomain</a> + </div> + + <div id="test17"> + <h5>URL with the same top level domain.</h5> + <a href="https://foobar.com/">Link</a> + <a href="https://www.foobar.com/">Link</a> + <a href="https://abc.def.foobar.com/">Link</a> + </div> + + <div id="test18"> + <h5>More than the threshold of links.</h5> + <a href="https://foobar1.com/">Link</a> + <a href="https://foobar1.com/">Duplicate Link</a> + <a href="https://foobar2.com/">Link</a> + <a href="https://foobar3.com/">Link</a> + <a href="https://foobar4.com/">Link</a> + <a href="https://foobar5.com/">Link</a> + <a href="https://foobar6.com/">Link</a> + <a href="https://foobar7.com/">Link</a> + <a href="https://foobar8.com/">Link</a> + <a href="https://foobar9.com/">Link</a> + <a href="https://foobar10.com/">Link</a> + <a href="https://foobar11.com/">Link Outside Threshold</a> + </div> + + <div id="test19"> + <h5>More than the threshold of links using multiple matching selectors.</h5> + <a class="foo" href="https://foobar1.com/">Link</a> + <a class="foo" href="https://foobar2.com/">Link</a> + <a class="foo" href="https://foobar3.com/">Link</a> + <a class="foo" href="https://foobar4.com/">Link</a> + <a class="foo" href="https://foobar5.com/">Link</a> + <a class="foo" href="https://foobar6.com/">Link</a> + <a class="foo" href="https://foobar7.com/">Link</a> + <a class="foo" href="https://foobar8.com/">Link</a> + <a class="foo" href="https://foobar9.com/">Link</a> + <a class="baz" href="https://foobaz1.com/">Link</a> + <a class="baz" href="https://foobaz2.com/">Link Outside Threshold</a> + </div> + + <div id="test20"> + <div id="b_results"> + <div class="b_algo"> + <div class="b_attribution"> + <cite>https://organic.com</cite> + </div> + </div> + </div> + </div> + + <div id="test21"> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored.com</cite> + </div> + </div> + </div> + </div> + + <div id="test22"> + <div class="adsMvCarousel"> + <cite>Fixed up from the carousel</cite> + </div> + </div> + + <div id="test23"> + <aside> + <cite>Fixed up from the sidebar</cite> + </aside> + </div> + + <div id="test24"> + <h5>More than the threshold of links using the text content selection method.</h5> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored1.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored2.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored3.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored4.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored5.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored6.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored7.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored8.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored9.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored10.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored11.com</cite> + </div> + </div> + </div> + </div> + + <div id="test25"> + <div id="b_results"> + <div class="b_algo"> + <div class="b_attribution"> + <cite>organic.com</cite> + </div> + </div> + </div> + </div> </div> </body> </html> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html b/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html index 7598da694e..f52088206f 100644 --- a/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html +++ b/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html @@ -204,7 +204,7 @@ } }) - window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("DOMContentLoaded", () => { let url = new URL(window.location.href); searchKey = url.searchParams.has("r") ? "r": "s"; @@ -219,7 +219,7 @@ updateSuggestions(); }); - window.addEventListener("popstate", (event) => { + window.addEventListener("popstate", () => { let baseUrl = new URL(window.location.href); let page = baseUrl.searchParams.get("page"); switch (page) { diff --git a/browser/components/search/test/browser/telemetry/searchTelemetry_redirect_with_js.html b/browser/components/search/test/browser/telemetry/searchTelemetry_redirect_with_js.html new file mode 100644 index 0000000000..744fcd2906 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/searchTelemetry_redirect_with_js.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Page will redirect using the url param</title> + <script> + let parentWindow = window.parent; + let params = new URLSearchParams(window.location.search); + let paramValue = params.get("url"); + if (paramValue) { + // Replicate how some SERPs load pages by encoding the true destination + // in the query param value. + let url = paramValue.startsWith("https://") ? + new URL(paramValue).href : + new URL(paramValue, "https://example.org/").href; + window.location.href = url; + } + </script> + </head> + <body> + <h1>Redirecting...</h1> + </body> +</html> diff --git a/browser/components/search/test/unit/domain_category_mappings_1a.json b/browser/components/search/test/unit/domain_category_mappings_1a.json deleted file mode 100644 index 51b18e12a7..0000000000 --- a/browser/components/search/test/unit/domain_category_mappings_1a.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Wrq9YDsieAMC3Y2DSY5Rcg==": [1, 100] -} diff --git a/browser/components/search/test/unit/domain_category_mappings_1b.json b/browser/components/search/test/unit/domain_category_mappings_1b.json deleted file mode 100644 index 698ef45f1a..0000000000 --- a/browser/components/search/test/unit/domain_category_mappings_1b.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "G99y4E1rUMgqSMfk3TjMaQ==": [2, 90] -} diff --git a/browser/components/search/test/unit/domain_category_mappings_2a.json b/browser/components/search/test/unit/domain_category_mappings_2a.json deleted file mode 100644 index 08db2fa8c2..0000000000 --- a/browser/components/search/test/unit/domain_category_mappings_2a.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Wrq9YDsieAMC3Y2DSY5Rcg==": [1, 80] -} diff --git a/browser/components/search/test/unit/domain_category_mappings_2b.json b/browser/components/search/test/unit/domain_category_mappings_2b.json deleted file mode 100644 index dec2d130c1..0000000000 --- a/browser/components/search/test/unit/domain_category_mappings_2b.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "G99y4E1rUMgqSMfk3TjMaQ==": [2, 50, 4, 80] -} diff --git a/browser/components/search/test/unit/test_search_telemetry_categorization_logic.js b/browser/components/search/test/unit/test_search_telemetry_categorization_logic.js index 947a7aae46..44b9147c50 100644 --- a/browser/components/search/test/unit/test_search_telemetry_categorization_logic.js +++ b/browser/components/search/test/unit/test_search_telemetry_categorization_logic.js @@ -15,90 +15,111 @@ ChromeUtils.defineESModuleGetters(this, { SearchSERPTelemetryUtils: "resource:///modules/SearchSERPTelemetry.sys.mjs", }); -const TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE = { - "byVQ4ej7T7s2xf/cPqgMyw==": [2, 90], - "1TEnSjgNCuobI6olZinMiQ==": [2, 95], - "/Bnju09b9iBPjg7K+5ENIw==": [2, 78, 4, 10], - "Ja6RJq5LQftdl7NQrX1avQ==": [2, 56, 4, 24], - "Jy26Qt99JrUderAcURtQ5A==": [2, 89], - "sZnJyyzY9QcN810Q6jfbvw==": [2, 43], - "QhmteGKeYk0okuB/bXzwRw==": [2, 65], - "CKQZZ1IJjzjjE4LUV8vUSg==": [2, 67], - "FK7mL5E1JaE6VzOiGMmlZg==": [2, 89], - "mzcR/nhDcrs0ed4kTf+ZFg==": [2, 99], -}; - -const TEST_DOMAIN_TO_CATEGORIES_MAP_INCONCLUSIVE = { - "IkOfhoSlHTMIZzWXkYf7fg==": [0, 0], - "PIAHxeaBOeDNY2tvZKqQuw==": [0, 0], - "DKx2mqmFtEvxrHAqpwSevA==": [0, 0], - "DlZKnz9ryYqbxJq9wodzlA==": [0, 0], - "n3NWT4N9JlKX0I7MUtAsYg==": [0, 0], - "A6KyupOlu5zXt8loti90qw==": [0, 0], - "gf5rpseruOaq8nXOSJPG3Q==": [0, 0], - "vlQYOvbcbAp6sMx54OwqCQ==": [0, 0], - "8PcaPATLgmHD9SR0/961Sw==": [0, 0], - "l+hLycEAW2v/OPE/XFpNwQ==": [0, 0], -}; - -const TEST_DOMAIN_TO_CATEGORIES_MAP_UNKNOWN_AND_INCONCLUSIVE = { - "CEA642T3hV+Fdi2PaRH9BQ==": [0, 0], - "cVqopYLASYxcWdDW4F+w2w==": [0, 0], - "X61OdTU20n8pxZ76K2eAHg==": [0, 0], - "/srrOggOAwgaBGCsPdC4bA==": [0, 0], - "onnMGn+MmaCQx3RNLBzGOQ==": [0, 0], -}; - -const TEST_DOMAIN_TO_CATEGORIES_MAP_ALL_TYPES = { - "VSXaqgDKYWrJ/yjsFomUdg==": [3, 90], - "6re74Kk34n2V6VCdLmCD5w==": [3, 88], - "s8gOGIaFnly5hHX7nPncnw==": [3, 90, 6, 2], - "zfRJyKV+2jd1RKNsSHm9pw==": [3, 78, 6, 7], - "zcW+KbRfLRO6Dljf5qnuwQ==": [3, 97], - "Rau9mfbBcIRiRQIliUxkow==": [0, 0], - "4AFhUOmLQ8804doOsI4jBA==": [0, 0], -}; - -const TEST_DOMAIN_TO_CATEGORIES_MAP_TIE = { - "fmEqRSc+pBr9noi0l99nGw==": [1, 50, 2, 50], - "cms8ipz0JQ3WS9o48RtvnQ==": [1, 50, 2, 50], - "y8Haj7Qdmx+k762RaxCPvA==": [1, 50, 2, 50], - "tCbLmi5xJ/OrF8tbRm8PrA==": [1, 50, 2, 50], - "uYNQECmDShqI409HrSTdLQ==": [1, 50, 2, 50], - "D88hdsmzLWIXYhkrDal33w==": [3, 50, 4, 50], - "1mhx0I0B4cEaI91x8zor7Q==": [5, 50, 6, 50], - "dVZYATQixuBHmalCFR9+Lw==": [7, 50, 8, 50], - "pdOFJG49D7hE/+FtsWDihQ==": [9, 50, 10, 50], - "+gl+dBhWE0nx0AM69m2g5w==": [11, 50, 12, 50], -}; - -const TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_1 = { - "VSXaqgDKYWrJ/yjsFomUdg==": [1, 45], - "6re74Kk34n2V6VCdLmCD5w==": [2, 45], - "s8gOGIaFnly5hHX7nPncnw==": [3, 45], - "zfRJyKV+2jd1RKNsSHm9pw==": [4, 45], - "zcW+KbRfLRO6Dljf5qnuwQ==": [5, 45], - "Rau9mfbBcIRiRQIliUxkow==": [6, 45], - "4AFhUOmLQ8804doOsI4jBA==": [7, 45], - "YZ3aEL73MR+Cjog0D7A24w==": [8, 45], - "crMclD9rwInEQ30DpZLg+g==": [9, 45], - "/r7oPRoE6LJAE95nuwmu7w==": [10, 45], -}; - -const TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_2 = { - "sHWSmFwSYL3snycBZCY8Kg==": [1, 35, 2, 4], - "FZ5zPYh6ByI0KGWKkmpDoA==": [1, 5, 2, 94], -}; +ChromeUtils.defineLazyGetter(this, "gCryptoHash", () => { + return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); +}); + +function convertDomainsToHashes(domainsToCategories) { + let newObj = {}; + for (let [key, value] of Object.entries(domainsToCategories)) { + gCryptoHash.init(gCryptoHash.SHA256); + let bytes = new TextEncoder().encode(key); + gCryptoHash.update(bytes, key.length); + let hash = gCryptoHash.finish(true); + newObj[hash] = value; + } + return newObj; +} + +const TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE = convertDomainsToHashes({ + "test1.com": [2, 90], + "test2.com": [2, 95], + "test3.com": [2, 78, 4, 10], + "test4.com": [2, 56, 4, 24], + "test5.com": [2, 89], + "test6.com": [2, 43], + "test7.com": [2, 65], + "test8.com": [2, 67], + "test9.com": [2, 89], + "test10.com": [2, 99], +}); + +const TEST_DOMAIN_TO_CATEGORIES_MAP_INCONCLUSIVE = convertDomainsToHashes({ + "test11.com": [0, 0], + "test12.com": [0, 0], + "test13.com": [0, 0], + "test14.com": [0, 0], + "test15.com": [0, 0], + "test16.com": [0, 0], + "test17.com": [0, 0], + "test18.com": [0, 0], + "test19.com": [0, 0], + "test20.com": [0, 0], +}); + +const TEST_DOMAIN_TO_CATEGORIES_MAP_UNKNOWN_AND_INCONCLUSIVE = + convertDomainsToHashes({ + "test31.com": [0, 0], + "test32.com": [0, 0], + "test33.com": [0, 0], + "test34.com": [0, 0], + "test35.com": [0, 0], + }); + +const TEST_DOMAIN_TO_CATEGORIES_MAP_ALL_TYPES = convertDomainsToHashes({ + "test51.com": [3, 90], + "test52.com": [3, 88], + "test53.com": [3, 90, 6, 2], + "test54.com": [3, 78, 6, 7], + "test55.com": [3, 97], + "test56.com": [0, 0], + "test57.com": [0, 0], +}); + +const TEST_DOMAIN_TO_CATEGORIES_MAP_TIE = convertDomainsToHashes({ + "test41.com": [1, 50, 2, 50], + "test42.com": [1, 50, 2, 50], + "test43.com": [1, 50, 2, 50], + "test44.com": [1, 50, 2, 50], + "test45.com": [1, 50, 2, 50], + "test46.com": [3, 50, 4, 50], + "test47.com": [5, 50, 6, 50], + "test48.com": [7, 50, 8, 50], + "test49.com": [9, 50, 10, 50], + "test50.com": [11, 50, 12, 50], +}); + +const TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_1 = + convertDomainsToHashes({ + "test51.com": [1, 45], + "test52.com": [2, 45], + "test53.com": [3, 45], + "test54.com": [4, 45], + "test55.com": [5, 45], + "test56.com": [6, 45], + "test57.com": [7, 45], + "test58.com": [8, 45], + "test59.com": [9, 45], + "test60.com": [10, 45], + }); + +const TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_2 = + convertDomainsToHashes({ + "test61.com": [1, 35, 2, 4], + "test62.com": [1, 5, 2, 94], + }); add_setup(async () => { + do_get_profile(); Services.prefs.setBoolPref( "browser.search.serpEventTelemetryCategorization.enabled", true ); + await SearchSERPDomainToCategoriesMap.init(); }); add_task(async function test_categorization_simple() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE ); @@ -115,8 +136,9 @@ add_task(async function test_categorization_simple() { "test10.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, @@ -126,7 +148,7 @@ add_task(async function test_categorization_simple() { }); add_task(async function test_categorization_inconclusive() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_INCONCLUSIVE ); @@ -143,8 +165,9 @@ add_task(async function test_categorization_inconclusive() { "test20.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, @@ -161,7 +184,7 @@ add_task(async function test_categorization_inconclusive() { add_task(async function test_categorization_unknown() { // Reusing TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE since none of this task's // domains will be keys within it. - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE ); @@ -178,8 +201,9 @@ add_task(async function test_categorization_unknown() { "test30.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, @@ -194,7 +218,7 @@ add_task(async function test_categorization_unknown() { }); add_task(async function test_categorization_unknown_and_inconclusive() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_UNKNOWN_AND_INCONCLUSIVE ); @@ -211,8 +235,9 @@ add_task(async function test_categorization_unknown_and_inconclusive() { "test40.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, @@ -228,7 +253,7 @@ add_task(async function test_categorization_unknown_and_inconclusive() { // Tests a mixture of categorized, inconclusive and unknown domains. add_task(async function test_categorization_all_types() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_ALL_TYPES ); @@ -247,8 +272,9 @@ add_task(async function test_categorization_all_types() { "test60.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, @@ -263,7 +289,7 @@ add_task(async function test_categorization_all_types() { }); add_task(async function test_categorization_tie() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_TIE ); @@ -280,8 +306,9 @@ add_task(async function test_categorization_tie() { "test50.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.equal( [1, 2].includes(resultsToReport.category), @@ -301,7 +328,7 @@ add_task(async function test_categorization_tie() { }); add_task(async function test_rank_penalization_equal_scores() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_1 ); @@ -318,8 +345,9 @@ add_task(async function test_rank_penalization_equal_scores() { "test60.com", ]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, @@ -329,14 +357,15 @@ add_task(async function test_rank_penalization_equal_scores() { }); add_task(async function test_rank_penalization_highest_score_lower_on_page() { - SearchSERPDomainToCategoriesMap.overrideMapForTests( + await SearchSERPDomainToCategoriesMap.overrideMapForTests( TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_2 ); let domains = new Set(["test61.com", "test62.com"]); - let resultsToReport = - SearchSERPCategorization.applyCategorizationLogic(domains); + let resultsToReport = await SearchSERPCategorization.applyCategorizationLogic( + domains + ); Assert.deepEqual( resultsToReport, diff --git a/browser/components/search/test/unit/test_search_telemetry_categorization_process_domains.js b/browser/components/search/test/unit/test_search_telemetry_categorization_process_domains.js deleted file mode 100644 index 84acedaa7a..0000000000 --- a/browser/components/search/test/unit/test_search_telemetry_categorization_process_domains.js +++ /dev/null @@ -1,89 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/* - * This test ensures we are correctly processing the domains that have been - * extracted from a SERP. - */ - -ChromeUtils.defineESModuleGetters(this, { - BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs", - SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs", - SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs", - SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", - sinon: "resource://testing-common/Sinon.sys.mjs", -}); - -// Links including the provider name are not extracted. -const PROVIDER = "example"; - -const TESTS = [ - { - title: "Domains matching the provider.", - domains: ["example.com", "www.example.com", "www.foobar.com"], - expected: ["foobar.com"], - }, - { - title: "Second-level domains to a top-level domain.", - domains: [ - "www.foobar.gc.ca", - "www.foobar.gov.uk", - "foobar.co.uk", - "www.foobar.co.il", - ], - expected: ["foobar.gc.ca", "foobar.gov.uk", "foobar.co.uk", "foobar.co.il"], - }, - { - title: "Long subdomain.", - domains: ["ab.cd.ef.gh.foobar.com"], - expected: ["foobar.com"], - }, - { - title: "Same top-level domain.", - domains: ["foobar.com", "www.foobar.com", "abc.def.foobar.com"], - expected: ["foobar.com"], - }, - { - title: "Empty input.", - domains: [""], - expected: [], - }, -]; - -add_setup(async function () { - Services.prefs.setBoolPref( - SearchUtils.BROWSER_SEARCH_PREF + "serpEventTelemetry.enabled", - true - ); - Services.prefs.setBoolPref( - SearchUtils.BROWSER_SEARCH_PREF + - "serpEventTelemetryCategorization.enabled", - true - ); - - // Required or else BrowserSearchTelemetry will throw. - sinon.stub(BrowserSearchTelemetry, "shouldRecordSearchCount").returns(true); - await SearchSERPTelemetry.init(); -}); - -add_task(async function test_parsing_extracted_urls() { - for (let i = 0; i < TESTS.length; i++) { - let currentTest = TESTS[i]; - let domains = new Set(currentTest.domains); - - if (currentTest.title) { - info(currentTest.title); - } - let expectedDomains = new Set(currentTest.expected); - let actualDomains = SearchSERPCategorization.processDomains( - domains, - PROVIDER - ); - - Assert.deepEqual( - Array.from(actualDomains), - Array.from(expectedDomains), - "Domains should have been parsed correctly." - ); - } -}); diff --git a/browser/components/search/test/unit/test_search_telemetry_categorization_sync.js b/browser/components/search/test/unit/test_search_telemetry_categorization_sync.js index 423ee0a81d..40d38efbba 100644 --- a/browser/components/search/test/unit/test_search_telemetry_categorization_sync.js +++ b/browser/components/search/test/unit/test_search_telemetry_categorization_sync.js @@ -16,16 +16,34 @@ ChromeUtils.defineESModuleGetters(this, { TestUtils: "resource://testing-common/TestUtils.sys.mjs", }); +ChromeUtils.defineLazyGetter(this, "gCryptoHash", () => { + return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); +}); + +function convertDomainsToHashes(domainsToCategories) { + let newObj = {}; + for (let [key, value] of Object.entries(domainsToCategories)) { + gCryptoHash.init(gCryptoHash.SHA256); + let bytes = new TextEncoder().encode(key); + gCryptoHash.update(bytes, key.length); + let hash = gCryptoHash.finish(true); + newObj[hash] = value; + } + return newObj; +} + async function waitForDomainToCategoriesUpdate() { return TestUtils.topicObserved("domain-to-categories-map-update-complete"); } -async function mockRecordWithCachedAttachment({ id, version, filename }) { +async function mockRecordWithCachedAttachment({ + id, + version, + filename, + mapping, +}) { // Get the bytes of the file for the hash and size for attachment metadata. - let data = await IOUtils.readUTF8( - PathUtils.join(do_get_cwd().path, filename) - ); - let buffer = new TextEncoder().encode(data).buffer; + let buffer = new TextEncoder().encode(JSON.stringify(mapping)).buffer; let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance( Ci.nsIArrayBufferInputStream ); @@ -73,21 +91,33 @@ const RECORDS = { id: RECORD_A_ID, version: 1, filename: "domain_category_mappings_1a.json", + mapping: convertDomainsToHashes({ + "example.com": [1, 100], + }), }, record1b: { id: RECORD_B_ID, version: 1, filename: "domain_category_mappings_1b.json", + mapping: convertDomainsToHashes({ + "example.org": [2, 90], + }), }, record2a: { id: RECORD_A_ID, version: 2, filename: "domain_category_mappings_2a.json", + mapping: convertDomainsToHashes({ + "example.com": [1, 80], + }), }, record2b: { id: RECORD_B_ID, version: 2, filename: "domain_category_mappings_2b.json", + mapping: convertDomainsToHashes({ + "example.org": [2, 50, 4, 80], + }), }, }; @@ -115,13 +145,13 @@ add_task(async function test_initial_import() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [{ category: 1, score: 100 }], "Return value from lookup of example.com should be the same." ); Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.org"), + await SearchSERPDomainToCategoriesMap.get("example.org"), [{ category: 2, score: 90 }], "Return value from lookup of example.org should be the same." ); @@ -167,13 +197,13 @@ add_task(async function test_update_records() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [{ category: 1, score: 80 }], "Return value from lookup of example.com should have changed." ); Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.org"), + await SearchSERPDomainToCategoriesMap.get("example.org"), [ { category: 2, score: 50 }, { category: 4, score: 80 }, @@ -224,13 +254,13 @@ add_task(async function test_delayed_initial_import() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [{ category: 1, score: 100 }], "Return value from lookup of example.com should be the same." ); Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.org"), + await SearchSERPDomainToCategoriesMap.get("example.org"), [{ category: 2, score: 90 }], "Return value from lookup of example.org should be the same." ); @@ -264,7 +294,7 @@ add_task(async function test_remove_record() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [{ category: 1, score: 80 }], "Initialized properly." ); @@ -283,13 +313,13 @@ add_task(async function test_remove_record() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [{ category: 1, score: 80 }], "Return value from lookup of example.com should remain unchanged." ); Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.org"), + await SearchSERPDomainToCategoriesMap.get("example.org"), [], "Return value from lookup of example.org should be empty." ); @@ -323,7 +353,7 @@ add_task(async function test_different_versions_coexisting() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [ { category: 1, @@ -334,7 +364,7 @@ add_task(async function test_different_versions_coexisting() { ); Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.org"), + await SearchSERPDomainToCategoriesMap.get("example.org"), [ { category: 2, score: 50 }, { category: 4, score: 80 }, @@ -367,7 +397,7 @@ add_task(async function test_download_error() { await promise; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [ { category: 1, @@ -406,7 +436,7 @@ add_task(async function test_download_error() { await observeDownloadError; Assert.deepEqual( - SearchSERPDomainToCategoriesMap.get("example.com"), + await SearchSERPDomainToCategoriesMap.get("example.com"), [], "Domain should not exist in store." ); diff --git a/browser/components/search/test/unit/xpcshell.toml b/browser/components/search/test/unit/xpcshell.toml index 61cdb83378..423d218d19 100644 --- a/browser/components/search/test/unit/xpcshell.toml +++ b/browser/components/search/test/unit/xpcshell.toml @@ -8,16 +8,8 @@ firefox-appdir = "browser" ["test_search_telemetry_categorization_logic.js"] -["test_search_telemetry_categorization_process_domains.js"] - ["test_search_telemetry_categorization_sync.js"] prefs = ["browser.search.serpEventTelemetryCategorization.enabled=true"] -support-files = [ - "domain_category_mappings_1a.json", - "domain_category_mappings_1b.json", - "domain_category_mappings_2a.json", - "domain_category_mappings_2b.json", -] ["test_search_telemetry_compare_urls.js"] |