diff --git a/app/build.gradle b/app/build.gradle index 4d74c4f867..b7c7f92591 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -741,14 +741,3 @@ ext.updateExtensionVersion = { task, extDir -> expand(values) } } - -tasks.register("updateAdsExtensionVersion", Copy) { task -> - updateExtensionVersion(task, 'src/main/assets/extensions/ads') -} - -tasks.register("updateCookiesExtensionVersion", Copy) { task -> - updateExtensionVersion(task, 'src/main/assets/extensions/cookies') -} - -preBuild.dependsOn "updateAdsExtensionVersion" -preBuild.dependsOn "updateCookiesExtensionVersion" diff --git a/app/metrics.yaml b/app/metrics.yaml index 4a4e765523..cf01315cb6 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -4235,11 +4235,12 @@ browser.search: - https://github.com/mozilla-mobile/fenix/pull/10112 - https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068 - https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789 + - https://github.com/mozilla-mobile/focus-android/pull/4968#issuecomment-879256443 data_sensitivity: - interaction notification_emails: - android-probes@mozilla.com - expires: "2021-08-01" + expires: "2022-07-01" ad_clicks: type: labeled_counter description: | @@ -4253,11 +4254,12 @@ browser.search: - https://github.com/mozilla-mobile/fenix/pull/10112 - https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068 - https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789 + - https://github.com/mozilla-mobile/focus-android/pull/4968#issuecomment-879256443 data_sensitivity: - interaction notification_emails: - android-probes@mozilla.com - expires: "2021-08-01" + expires: "2022-07-01" in_content: type: labeled_counter description: | @@ -4270,11 +4272,12 @@ browser.search: - https://github.com/mozilla-mobile/fenix/pull/10167 - https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068 - https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789 + - https://github.com/mozilla-mobile/focus-android/pull/4968#issuecomment-879256443 data_sensitivity: - interaction notification_emails: - android-probes@mozilla.com - expires: "2021-08-01" + expires: "2022-07-01" addons: open_addons_in_settings: diff --git a/app/src/main/assets/extensions/ads/ads.js b/app/src/main/assets/extensions/ads/ads.js deleted file mode 100644 index 356bda2d27..0000000000 --- a/app/src/main/assets/extensions/ads/ads.js +++ /dev/null @@ -1,61 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const ADLINK_CHECK_TIMEOUT_MS = 1000; - -function collectLinks(urls) { - let anchors = document.getElementsByTagName("a"); - for (let anchor of anchors) { - if (!anchor.href) { - continue; - } - urls.push(anchor.href); - } -} - -function sendLinks(cookies) { - let urls = []; - collectLinks(urls); - - let message = { - 'url': document.location.href, - 'urls': urls, - 'cookies': cookies - }; - browser.runtime.sendNativeMessage("MozacBrowserAds", message); -} - -function notify(message) { - sendLinks(message.cookies); -} - -browser.runtime.onMessage.addListener(notify); - -const events = ["pageshow", "load", "unload"]; -var timeout; - -const eventLogger = event => { - switch (event.type) { - case "load": - timeout = setTimeout(() => { - browser.runtime.sendMessage({ "checkCookies": true }); - }, ADLINK_CHECK_TIMEOUT_MS) - break; - case "pageshow": - if (event.persisted) { - timeout = setTimeout(() => { - browser.runtime.sendMessage({ "checkCookies": true }); - }, ADLINK_CHECK_TIMEOUT_MS) - } - break; - case "unload": - clearTimeout(timeout); - default: - console.log('Event:', event.type); - } -}; - -events.forEach(eventName => - window.addEventListener(eventName, eventLogger) -); diff --git a/app/src/main/assets/extensions/ads/adsBackground.js b/app/src/main/assets/extensions/ads/adsBackground.js deleted file mode 100644 index 63b6f0fb93..0000000000 --- a/app/src/main/assets/extensions/ads/adsBackground.js +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -browser.runtime.onMessage.addListener(notify); - -function sendMessageToTabs(tabs, cookies) { - for (let tab of tabs) { - browser.tabs.sendMessage( - tab.id, - { cookies } - ); - } -} - -function notify(message) { - if (message.checkCookies) { - browser.cookies.getAll({}) - .then(cookies => { - browser.tabs.query({ - currentWindow: true, - active: true - }).then(tabs => { - sendMessageToTabs(tabs, cookies); - }); - }); - } -} diff --git a/app/src/main/assets/extensions/ads/manifest.template.json b/app/src/main/assets/extensions/ads/manifest.template.json deleted file mode 100644 index b098b593b1..0000000000 --- a/app/src/main/assets/extensions/ads/manifest.template.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "manifest_version": 2, - "applications": { - "gecko": { - "id": "ads@mozac.org" - } - }, - "name": "Mozilla Android Components - Ads", - "version": "${version}", - "content_scripts": [ - { - "matches": ["https://*/*"], - "include_globs": [ - "https://www.google.*/search*", - "https://www.bing.com/search*", - "https://www.baidu.com/*", - "https://m.baidu.com/*", - "https://duckduckgo.com/*" - ], - "js": ["ads.js"], - "run_at": "document_end" - } - ], - "background": { - "scripts": ["adsBackground.js"] - }, - "permissions": [ - "geckoViewAddons", - "nativeMessaging", - "nativeMessagingFromContent", - "geckoViewAddons", - "nativeMessaging", - "nativeMessagingFromContent", - "webNavigation", - "webRequest", - "webRequestBlocking", - "cookies", - "*://*/*" - ] -} diff --git a/app/src/main/assets/extensions/cookies/cookies.js b/app/src/main/assets/extensions/cookies/cookies.js deleted file mode 100644 index d5ac33bd28..0000000000 --- a/app/src/main/assets/extensions/cookies/cookies.js +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const COOKIES_CHECK_TIMEOUT_MS = 1000; - -function sendCookies(cookies) { - let message = { - 'url': document.location.href, - 'cookies': cookies - } - browser.runtime.sendNativeMessage("BrowserCookiesMessage", message); -} - -function notify(message) { - sendCookies(message.cookies); -} - -browser.runtime.onMessage.addListener(notify); - -const events = ["pageshow", "load", "unload"]; -var timeout; - -const eventLogger = event => { - switch (event.type) { - case "load": - timeout = setTimeout(() => { - browser.runtime.sendMessage({"checkCookies": true}); - }, COOKIES_CHECK_TIMEOUT_MS); - break; - case "pageshow": - if (event.persisted) { - timeout = setTimeout(() => { - browser.runtime.sendMessage({"checkCookies": true}); - }, COOKIES_CHECK_TIMEOUT_MS); - } - break; - case "unload": - clearTimeout(timeout); - default: - console.log('Event:', event.type); - } -}; - -events.forEach(eventName => - window.addEventListener(eventName, eventLogger) -); diff --git a/app/src/main/assets/extensions/cookies/cookiesBackground.js b/app/src/main/assets/extensions/cookies/cookiesBackground.js deleted file mode 100644 index 755eb6909a..0000000000 --- a/app/src/main/assets/extensions/cookies/cookiesBackground.js +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -browser.runtime.onMessage.addListener(notify); - -function sendMessageToTabs(tabs, cookies) { - for (let tab of tabs) { - browser.tabs.sendMessage( - tab.id, - {cookies: cookies} - ); - } -} - -function notify(message) { - if(message.checkCookies) { - browser.cookies.getAll({}) - .then(cookies => { - browser.tabs.query({ - currentWindow: true, - active: true - }).then(tabs => { - sendMessageToTabs(tabs, cookies); - }); - }); - } -} \ No newline at end of file diff --git a/app/src/main/assets/extensions/cookies/manifest.template.json b/app/src/main/assets/extensions/cookies/manifest.template.json deleted file mode 100644 index f202a32f16..0000000000 --- a/app/src/main/assets/extensions/cookies/manifest.template.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "manifest_version": 2, - "applications": { - "gecko": { - "id": "cookies@mozac.org" - } - }, - "name": "Mozilla Android Components - Cookies", - "version": "${version}", - "content_scripts": [ - { - "matches": ["https://*/*"], - "include_globs": [ - "https://www.google.*/search*", - "https://www.baidu.com/*", - "https://m.baidu.com/*", - "https://*search.yahoo.com/search*", - "https://www.bing.com/search*", - "https://duckduckgo.com/*" - ], - "js": ["cookies.js"], - "run_at": "document_end" - } - ], - "background": { - "scripts": ["cookiesBackground.js"] - }, - "permissions": [ - "geckoViewAddons", - "nativeMessaging", - "nativeMessagingFromContent", - "webNavigation", - "webRequest", - "webRequestBlocking", - "cookies", - "*://*/*" - ] -} diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 975c6d24a3..62227807f7 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -39,8 +39,11 @@ import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.readerview.ReaderViewMiddleware import mozilla.components.feature.recentlyclosed.RecentlyClosedMiddleware +import mozilla.components.feature.search.middleware.AdsTelemetryMiddleware import mozilla.components.feature.search.middleware.SearchMiddleware import mozilla.components.feature.search.region.RegionMiddleware +import mozilla.components.feature.search.telemetry.ads.AdsTelemetry +import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry import mozilla.components.feature.session.HistoryDelegate import mozilla.components.feature.session.middleware.LastAccessMiddleware import mozilla.components.feature.session.middleware.undo.UndoMiddleware @@ -76,8 +79,6 @@ import org.mozilla.fenix.historymetadata.HistoryMetadataService import org.mozilla.fenix.media.MediaSessionService import org.mozilla.fenix.perf.StrictModeManager import org.mozilla.fenix.perf.lazyMonitored -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry -import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.telemetry.TelemetryMiddleware @@ -195,7 +196,6 @@ class Core( ReaderViewMiddleware(), TelemetryMiddleware( context.settings(), - adsTelemetry, metrics ), ThumbnailsMiddleware(thumbnailStorage), @@ -207,7 +207,8 @@ class Core( migration = SearchMigration(context) ), RecordingDevicesMiddleware(context), - PromptMiddleware() + PromptMiddleware(), + AdsTelemetryMiddleware(adsTelemetry) ) if (FeatureFlags.historyMetadataFeature) { @@ -268,11 +269,11 @@ class Core( } val adsTelemetry by lazyMonitored { - AdsTelemetry(metrics) + AdsTelemetry() } val searchTelemetry by lazyMonitored { - InContentTelemetry(metrics) + InContentTelemetry() } /** diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt index c38ebb62da..c51192b4b5 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/MetricController.kt @@ -23,6 +23,8 @@ import mozilla.components.feature.media.facts.MediaFacts import mozilla.components.feature.prompts.facts.LoginDialogFacts import mozilla.components.feature.prompts.facts.CreditCardAutofillDialogFacts import mozilla.components.feature.pwa.ProgressiveWebAppFacts +import mozilla.components.feature.search.telemetry.ads.AdsTelemetry +import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry import mozilla.components.feature.syncedtabs.facts.SyncedTabsFacts import mozilla.components.feature.top.sites.facts.TopSitesFacts import mozilla.components.lib.dataprotect.SecurePrefsReliabilityExperiment @@ -315,6 +317,15 @@ internal class ReleaseMetricController( Event.SecurePrefsReset } + Component.FEATURE_SEARCH to AdsTelemetry.SERP_ADD_CLICKED -> { + Event.SearchAdClicked(value!!) + } + Component.FEATURE_SEARCH to AdsTelemetry.SERP_SHOWN_WITH_ADDS -> { + Event.SearchWithAds(value!!) + } + Component.FEATURE_SEARCH to InContentTelemetry.IN_CONTENT_SEARCH -> { + Event.SearchInContent(value!!) + } else -> null } diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt deleted file mode 100644 index 385fed6d07..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetry.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry - -import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import mozilla.components.browser.state.state.BrowserState -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine -import mozilla.components.concept.engine.EngineSession -import mozilla.components.concept.engine.webextension.MessageHandler -import mozilla.components.concept.engine.webextension.WebExtension -import mozilla.components.lib.state.ext.flowScoped -import mozilla.components.support.base.log.logger.Logger -import mozilla.components.support.ktx.android.org.json.toList -import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged -import org.json.JSONObject - -abstract class BaseSearchTelemetry { - - @VisibleForTesting - internal val providerList = listOf( - SearchProviderModel( - name = "google", - regexp = "^https:\\/\\/www\\.google\\.(?:.+)\\/search", - queryParam = "q", - codeParam = "client", - codePrefixes = listOf("firefox"), - followOnParams = listOf("oq", "ved", "ei"), - extraAdServersRegexps = listOf("^https?:\\/\\/www\\.google(?:adservices)?\\.com\\/(?:pagead\\/)?aclk") - ), - SearchProviderModel( - name = "duckduckgo", - regexp = "^https:\\/\\/duckduckgo\\.com\\/", - queryParam = "q", - codeParam = "t", - codePrefixes = listOf("f"), - extraAdServersRegexps = listOf( - "^https:\\/\\/duckduckgo.com\\/y\\.js", - "^https:\\/\\/www\\.amazon\\.(?:[a-z.]{2,24}).*(?:tag=duckduckgo-)" - ) - ), - SearchProviderModel( - name = "yahoo", - regexp = "^https:\\/\\/(?:.*)search\\.yahoo\\.com\\/search", - queryParam = "p" - ), - SearchProviderModel( - name = "baidu", - regexp = "^https:\\/\\/m\\.baidu\\.com(?:.*)\\/s", - queryParam = "word", - codeParam = "from", - codePrefixes = listOf("1000969a"), - followOnParams = listOf("oq") - ), - SearchProviderModel( - name = "bing", - regexp = "^https:\\/\\/www\\.bing\\.com\\/search", - queryParam = "q", - codeParam = "pc", - codePrefixes = listOf("MOZ", "MZ"), - followOnCookies = listOf( - SearchProviderCookie( - extraCodeParam = "form", - extraCodePrefixes = listOf("QBRE"), - host = "www.bing.com", - name = "SRCHS", - codeParam = "PC", - codePrefixes = listOf("MOZ", "MZ") - ) - ), - extraAdServersRegexps = listOf( - "^https:\\/\\/www\\.bing\\.com\\/acli?c?k", - "^https:\\/\\/www\\.bing\\.com\\/fd\\/ls\\/GLinkPingPost\\.aspx.*acli?c?k" - ) - ) - ) - - abstract fun install(engine: Engine, store: BrowserStore) - - internal fun getProviderForUrl(url: String): SearchProviderModel? = - providerList.find { provider -> provider.regexp.containsMatchIn(url) } - - @OptIn(ExperimentalCoroutinesApi::class) - internal fun installWebExtension( - engine: Engine, - store: BrowserStore, - extensionInfo: ExtensionInfo - ) { - engine.installWebExtension( - id = extensionInfo.id, - url = extensionInfo.resourceUrl, - onSuccess = { extension -> - store.flowScoped { flow -> subscribeToUpdates(flow, extension, extensionInfo) } - }, - onError = { _, throwable -> - Logger.error("Could not install ${extensionInfo.id} extension", throwable) - }) - } - - private suspend fun subscribeToUpdates( - flow: Flow, - extension: WebExtension, - extensionInfo: ExtensionInfo - ) { - // Whenever we see a new EngineSession in the store then we register our content message - // handler if it has not been added yet. - flow.map { it.tabs } - .filterChanged { it.engineState.engineSession } - .collect { state -> - val engineSession = state.engineState.engineSession ?: return@collect - - if (extension.hasContentMessageHandler(engineSession, extensionInfo.messageId)) { - return@collect - } - extension.registerContentMessageHandler( - engineSession, - extensionInfo.messageId, - SearchTelemetryMessageHandler() - ) - } - } - - protected fun getMessageList(message: JSONObject, key: String): List { - return message.getJSONArray(key).toList() - } - - /** - * This method is used to process any valid json message coming from a web-extension - */ - @VisibleForTesting - internal abstract fun processMessage(message: JSONObject) - - @VisibleForTesting - internal inner class SearchTelemetryMessageHandler : MessageHandler { - - override fun onMessage(message: Any, source: EngineSession?): Any? { - if (message is JSONObject) { - processMessage(message) - } else { - throw IllegalStateException("Received unexpected message: $message") - } - - // Needs to return something that is not null and not Unit: - // https://github.com/mozilla-mobile/android-components/issues/2969 - return "" - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/ExtensionInfo.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/ExtensionInfo.kt deleted file mode 100644 index dd01b15158..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/ExtensionInfo.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry - -data class ExtensionInfo( - internal val id: String, - internal val resourceUrl: String, - internal val messageId: String -) diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/SearchProviderCookie.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/SearchProviderCookie.kt deleted file mode 100644 index 5e4372e9eb..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/SearchProviderCookie.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry - -data class SearchProviderCookie( - val extraCodeParam: String, - val extraCodePrefixes: List, - val host: String, - val name: String, - val codeParam: String, - val codePrefixes: List -) diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/SearchProviderModel.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/SearchProviderModel.kt deleted file mode 100644 index 3017804f66..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/SearchProviderModel.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry - -data class SearchProviderModel( - val name: String, - val regexp: Regex, - val queryParam: String, - val codeParam: String, - val codePrefixes: List, - val followOnParams: List, - val extraAdServersRegexps: List, - val followOnCookies: List -) { - - constructor( - name: String, - regexp: String, - queryParam: String, - codeParam: String = "", - codePrefixes: List = emptyList(), - followOnParams: List = emptyList(), - extraAdServersRegexps: List = emptyList(), - followOnCookies: List = emptyList() - ) : this( - name = name, - regexp = regexp.toRegex(), - queryParam = queryParam, - codeParam = codeParam, - codePrefixes = codePrefixes, - followOnParams = followOnParams, - extraAdServersRegexps = extraAdServersRegexps.map { it.toRegex() }, - followOnCookies = followOnCookies - ) - - /** - * Checks if any of the given URLs represent an ad from the search engine. - * Used to check if a clicked link was for an ad. - */ - fun containsAdLinks(urlList: List) = urlList.any { url -> isAd(url) } - - private fun isAd(url: String) = - extraAdServersRegexps.any { adsRegex -> adsRegex.containsMatchIn(url) } -} diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/TrackKeyInfo.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/TrackKeyInfo.kt deleted file mode 100644 index 725e3ec553..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/TrackKeyInfo.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry - -import java.util.Locale - -/** - * A data class that tracks key information about a Search Engine Result Page (SERP). - * - * @property provider The name of the search provider. - * @property type The search access point type (SAP). This is either "organic", "sap" or - * "sap-follow-on". - * @property code The search URL's `code` query parameter. - * @property channel The search URL's `channel` query parameter. - */ -internal data class TrackKeyInfo( - var provider: String, - var type: String, - var code: String?, - var channel: String? = null -) { - /** - * Returns the track key information into the following string format: - * `.in-content.[sap|sap-follow-on|organic].[code|none](.[channel])?`. - */ - fun createTrackKey(): String { - return "${provider.toLowerCase(Locale.ROOT)}.in-content" + - ".${type.toLowerCase(Locale.ROOT)}" + - ".${code?.toLowerCase(Locale.ROOT) ?: "none"}" + - if (!channel?.toLowerCase(Locale.ROOT).isNullOrBlank()) - ".${channel?.toLowerCase(Locale.ROOT)}" - else "" - } -} diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/Utils.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/Utils.kt deleted file mode 100644 index 2ab395f1b8..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/Utils.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry - -import android.net.Uri -import org.json.JSONObject - -private const val SEARCH_TYPE_SAP_FOLLOW_ON = "sap-follow-on" -private const val SEARCH_TYPE_SAP = "sap" -private const val SEARCH_TYPE_ORGANIC = "organic" -private const val CHANNEL_KEY = "channel" - -internal fun getTrackKey( - provider: SearchProviderModel, - uri: Uri, - cookies: List -): String { - val paramSet = uri.queryParameterNames - var code: String? = null - - if (provider.codeParam.isNotEmpty()) { - code = uri.getQueryParameter(provider.codeParam) - if (code.isNullOrEmpty() && - provider.name == "baidu" && - uri.toString().contains("from=")) { - code = uri.toString().substringAfter("from=", "") - .substringBefore("/", "") - } - - // Glean doesn't allow code starting with a figure - if (code != null && code.isNotEmpty()) { - val codeStart = code.first() - if (codeStart.isDigit()) { - code = "_$code" - } - } - - // Try cookies first because Bing has followOnCookies and valid code, but no - // followOnParams => would tracks organic instead of sap-follow-on - if (provider.followOnCookies.isNotEmpty()) { - // Checks if engine contains a valid follow-on cookie, otherwise return default - getTrackKeyFromCookies(provider, uri, cookies)?.let { - return it.createTrackKey() - } - } - - // For Bing if it didn't have a valid cookie and for all the other search engines - if (hasValidCode(uri.getQueryParameter(provider.codeParam), provider)) { - val channel = uri.getQueryParameter(CHANNEL_KEY) - val type = getSapType(provider.followOnParams, paramSet) - return TrackKeyInfo(provider.name, type, code, channel).createTrackKey() - } - } - - // Default to organic search type if no code parameter was found. - return TrackKeyInfo(provider.name, SEARCH_TYPE_ORGANIC, code).createTrackKey() -} - -private fun getTrackKeyFromCookies( - provider: SearchProviderModel, - uri: Uri, - cookies: List -): TrackKeyInfo? { - // Especially Bing requires lots of extra work related to cookies. - for (followOnCookie in provider.followOnCookies) { - val eCode = uri.getQueryParameter(followOnCookie.extraCodeParam) - if (eCode == null || !followOnCookie.extraCodePrefixes.any { prefix -> - eCode.startsWith(prefix) - }) { - continue - } - - // If this cookie is present, it's probably an SAP follow-on. - // This might be an organic follow-on in the same session, but there - // is no way to tell the difference. - for (cookie in cookies) { - if (cookie.getString("name") != followOnCookie.name) { - continue - } - val valueList = cookie.getString("value") - .split("=") - .map { item -> item.trim() } - - if (valueList.size == 2 && valueList[0] == followOnCookie.codeParam && - followOnCookie.codePrefixes.any { prefix -> - valueList[1].startsWith( - prefix - ) - } - ) { - return TrackKeyInfo(provider.name, SEARCH_TYPE_SAP_FOLLOW_ON, valueList[1]) - } - } - } - - return null -} - -private fun getSapType(followOnParams: List, paramSet: Set): String { - return if (followOnParams.any { param -> paramSet.contains(param) }) { - SEARCH_TYPE_SAP_FOLLOW_ON - } else { - SEARCH_TYPE_SAP - } -} - -private fun hasValidCode(code: String?, provider: SearchProviderModel): Boolean = - code != null && provider.codePrefixes.any { prefix -> code.startsWith(prefix) } diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt deleted file mode 100644 index 055c9a77e0..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetry.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry.ads - -import androidx.annotation.VisibleForTesting -import androidx.core.net.toUri -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine -import org.json.JSONObject -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.search.telemetry.BaseSearchTelemetry -import org.mozilla.fenix.search.telemetry.ExtensionInfo -import org.mozilla.fenix.search.telemetry.getTrackKey - -class AdsTelemetry(private val metrics: MetricController) : BaseSearchTelemetry() { - - // Cache the cookies provided by the ADS_EXTENSION_ID extension to be used when tracking - // the Ads clicked telemetry. - var cachedCookies = listOf() - - override fun install( - engine: Engine, - store: BrowserStore - ) { - val info = ExtensionInfo( - id = ADS_EXTENSION_ID, - resourceUrl = ADS_EXTENSION_RESOURCE_URL, - messageId = ADS_MESSAGE_ID - ) - installWebExtension(engine, store, info) - } - - override fun processMessage(message: JSONObject) { - // Cache the cookies list when the extension sends a message. - cachedCookies = getMessageList( - message, - ADS_MESSAGE_COOKIES_KEY - ) - - val urls = getMessageList(message, ADS_MESSAGE_DOCUMENT_URLS_KEY) - val provider = getProviderForUrl(message.getString(ADS_MESSAGE_SESSION_URL_KEY)) - - provider?.let { - if (it.containsAdLinks(urls)) { - metrics.track(Event.SearchWithAds(it.name)) - } - } - } - - /** - * If a search ad is clicked, record the search ad that was clicked. This method is called - * when the browser is navigating to a new URL, which may be a search ad. - * - * @param url The URL of the page before the search ad was clicked. This is used to determine - * the originating search provider. - * @param urlPath A list of the URLs and load requests collected in between location changes. - * Clicking on a search ad generates a list of redirects from the originating search provider - * to the ad source. This is used to determine if there was an ad click. - */ - fun trackAdClickedMetric(url: String?, urlPath: List) { - val uri = url?.toUri() ?: return - val provider = getProviderForUrl(url) ?: return - val paramSet = uri.queryParameterNames - - if (!paramSet.contains(provider.queryParam) || !provider.containsAdLinks(urlPath)) { - // Do nothing if the URL does not have the search provider's query parameter or - // there were no ad clicks. - return - } - - metrics.track(Event.SearchAdClicked(getTrackKey(provider, uri, cachedCookies))) - } - - companion object { - @VisibleForTesting - internal const val ADS_EXTENSION_ID = "ads@mozac.org" - - @VisibleForTesting - internal const val ADS_EXTENSION_RESOURCE_URL = "resource://android/assets/extensions/ads/" - - @VisibleForTesting - internal const val ADS_MESSAGE_SESSION_URL_KEY = "url" - - @VisibleForTesting - internal const val ADS_MESSAGE_DOCUMENT_URLS_KEY = "urls" - - @VisibleForTesting - internal const val ADS_MESSAGE_ID = "MozacBrowserAds" - - @VisibleForTesting - internal const val ADS_MESSAGE_COOKIES_KEY = "cookies" - } -} diff --git a/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt b/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt deleted file mode 100644 index 2891dda0d5..0000000000 --- a/app/src/main/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetry.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry.incontent - -import androidx.annotation.VisibleForTesting -import androidx.core.net.toUri -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine -import org.json.JSONObject -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.search.telemetry.BaseSearchTelemetry -import org.mozilla.fenix.search.telemetry.ExtensionInfo -import org.mozilla.fenix.search.telemetry.getTrackKey - -class InContentTelemetry(private val metrics: MetricController) : BaseSearchTelemetry() { - - override fun install(engine: Engine, store: BrowserStore) { - val info = ExtensionInfo( - id = COOKIES_EXTENSION_ID, - resourceUrl = COOKIES_EXTENSION_RESOURCE_URL, - messageId = COOKIES_MESSAGE_ID - ) - installWebExtension(engine, store, info) - } - - override fun processMessage(message: JSONObject) { - val cookies = getMessageList( - message, - COOKIES_MESSAGE_LIST_KEY - ) - trackPartnerUrlTypeMetric(message.getString(COOKIES_MESSAGE_SESSION_URL_KEY), cookies) - } - - @VisibleForTesting - internal fun trackPartnerUrlTypeMetric(url: String, cookies: List) { - val provider = getProviderForUrl(url) ?: return - val uri = url.toUri() - val paramSet = uri.queryParameterNames - - if (!paramSet.contains(provider.queryParam)) { - return - } - - metrics.track(Event.SearchInContent(getTrackKey(provider, uri, cookies))) - } - - companion object { - @VisibleForTesting - internal const val COOKIES_EXTENSION_ID = "cookies@mozac.org" - - @VisibleForTesting - internal const val COOKIES_EXTENSION_RESOURCE_URL = - "resource://android/assets/extensions/cookies/" - - @VisibleForTesting - internal const val COOKIES_MESSAGE_SESSION_URL_KEY = "url" - - @VisibleForTesting - internal const val COOKIES_MESSAGE_LIST_KEY = "cookies" - - @VisibleForTesting - internal const val COOKIES_MESSAGE_ID = "BrowserCookiesMessage" - } -} diff --git a/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt b/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt index 05a5955296..97bd6d42a0 100644 --- a/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt +++ b/app/src/main/java/org/mozilla/fenix/telemetry/TelemetryMiddleware.kt @@ -4,7 +4,6 @@ package org.mozilla.fenix.telemetry -import androidx.annotation.VisibleForTesting import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.DownloadAction @@ -16,13 +15,13 @@ import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.EngineState import mozilla.components.browser.state.state.SessionState +import mozilla.components.feature.search.telemetry.ads.AdsTelemetry import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext import mozilla.components.support.base.android.Clock import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.GleanMetrics.EngineTab as EngineMetrics @@ -35,26 +34,11 @@ import org.mozilla.fenix.GleanMetrics.EngineTab as EngineMetrics */ class TelemetryMiddleware( private val settings: Settings, - private val adsTelemetry: AdsTelemetry, private val metrics: MetricController ) : Middleware { private val logger = Logger("TelemetryMiddleware") - @VisibleForTesting - internal val redirectChains = mutableMapOf() - - /** - * Utility to collect URLs / load requests in between location changes. - */ - internal class RedirectChain(internal val root: String) { - internal val chain = mutableListOf() - - fun add(url: String) { - chain.add(url) - } - } - @Suppress("TooGenericExceptionCaught", "ComplexMethod", "NestedBlockDepth") override fun invoke( context: MiddlewareContext, @@ -75,28 +59,6 @@ class TelemetryMiddleware( } } } - is ContentAction.UpdateLoadRequestAction -> { - context.state.findTab(action.sessionId)?.let { tab -> - // Collect all load requests in between location changes - if (!redirectChains.containsKey(action.sessionId) && action.loadRequest.url != tab.content.url) { - redirectChains[action.sessionId] = RedirectChain(tab.content.url) - } - - redirectChains[action.sessionId]?.add(action.loadRequest.url) - } - } - is ContentAction.UpdateUrlAction -> { - redirectChains[action.sessionId]?.let { - // Record ads telemetry providing all redirects - try { - adsTelemetry.trackAdClickedMetric(it.root, it.chain) - } catch (t: Throwable) { - logger.info("Failed to record search telemetry", t) - } finally { - redirectChains.remove(action.sessionId) - } - } - } is DownloadAction.AddDownloadAction -> { metrics.track(Event.DownloadAdded) } diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchProviderModelTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchProviderModelTest.kt deleted file mode 100644 index e9d8561496..0000000000 --- a/app/src/test/java/org/mozilla/fenix/search/SearchProviderModelTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search - -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.mozilla.fenix.search.telemetry.SearchProviderModel - -class SearchProviderModelTest { - - private val testSearchProvider = - SearchProviderModel( - name = "test", - regexp = "test", - queryParam = "test", - codeParam = "test", - codePrefixes = listOf(), - followOnParams = listOf(), - extraAdServersRegexps = listOf( - "^https:\\/\\/www\\.bing\\.com\\/acli?c?k", - "^https:\\/\\/www\\.bing\\.com\\/fd\\/ls\\/GLinkPingPost\\.aspx.*acli?c?k" - ) - ) - - @Test - fun `test search provider contains ads`() { - val ad = "https://www.bing.com/aclick" - val nonAd = "https://www.bing.com/notanad" - assertTrue(testSearchProvider.containsAdLinks(listOf(ad, nonAd))) - } - - @Test - fun `test search provider does not contain ads`() { - val nonAd1 = "https://www.yahoo.com/notanad" - val nonAd2 = "https://www.google.com/" - assertFalse(testSearchProvider.containsAdLinks(listOf(nonAd1, nonAd2))) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt b/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt deleted file mode 100644 index fd87665d43..0000000000 --- a/app/src/test/java/org/mozilla/fenix/search/telemetry/BaseSearchTelemetryTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.mozilla.fenix.search.telemetry - -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine -import org.json.JSONObject -import org.junit.Assert.assertEquals -import org.junit.Test - -class BaseSearchTelemetryTest { - - private lateinit var baseTelemetry: BaseSearchTelemetry - private lateinit var handler: BaseSearchTelemetry.SearchTelemetryMessageHandler - - @org.junit.Before - fun setUp() { - baseTelemetry = spyk(object : BaseSearchTelemetry() { - - override fun install(engine: Engine, store: BrowserStore) { - // mock, do nothing - } - - override fun processMessage(message: JSONObject) { - // mock, do nothing - } - }) - handler = baseTelemetry.SearchTelemetryMessageHandler() - } - - @Test - fun install() { - val engine = mockk(relaxed = true) - val store = mockk(relaxed = true) - val id = "id" - val resourceUrl = "resourceUrl" - val messageId = "messageId" - val extensionInfo = ExtensionInfo(id, resourceUrl, messageId) - - baseTelemetry.installWebExtension(engine, store, extensionInfo) - - verify { - engine.installWebExtension( - id = id, - url = resourceUrl, - onSuccess = any(), - onError = any() - ) - } - } - - @Test - fun `get provider for google url`() { - val url = "https://www.google.com/search?q=computers" - - assertEquals("google", baseTelemetry.getProviderForUrl(url)?.name) - } - - @Test - fun `message handler finds a valid json object`() { - val message = JSONObject() - - handler.onMessage(message, mockk()) - - verify { baseTelemetry.processMessage(message) } - } - - @Test(expected = IllegalStateException::class) - fun `message handler finds no json object`() { - val message = "message" - - handler.onMessage(message, mockk()) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetryTest.kt b/app/src/test/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetryTest.kt deleted file mode 100644 index c859dab52a..0000000000 --- a/app/src/test/java/org/mozilla/fenix/search/telemetry/ads/AdsTelemetryTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.search.telemetry.ads - -import io.mockk.mockk -import io.mockk.slot -import io.mockk.spyk -import io.mockk.verify -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine -import org.json.JSONArray -import org.json.JSONObject -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import org.mozilla.fenix.search.telemetry.ExtensionInfo -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_EXTENSION_ID -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_EXTENSION_RESOURCE_URL -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_MESSAGE_COOKIES_KEY -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_MESSAGE_DOCUMENT_URLS_KEY -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_MESSAGE_ID -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_MESSAGE_SESSION_URL_KEY - -@RunWith(FenixRobolectricTestRunner::class) -class AdsTelemetryTest { - - private val metrics: MetricController = mockk(relaxed = true) - private lateinit var ads: AdsTelemetry - - @Before - fun setUp() { - ads = spyk(AdsTelemetry(metrics)) - } - - @Test - fun `don't track with null session url`() { - ads.trackAdClickedMetric(null, listOf()) - - verify(exactly = 0) { ads.getProviderForUrl(any()) } - } - - @Test - fun `don't track when no ads are in the redirect path`() { - val sessionUrl = "https://www.google.com/search?q=aaa" - - ads.trackAdClickedMetric(sessionUrl, listOf("https://www.aaa.com")) - - verify(exactly = 0) { metrics.track(any()) } - } - - @Test - fun `track when ads are in the redirect path`() { - val sessionUrl = "https://www.google.com/search?q=aaa" - - ads.trackAdClickedMetric( - sessionUrl, - listOf("https://www.google.com/aclk", "https://www.aaa.com") - ) - - verify { metrics.track(Event.SearchAdClicked("google.in-content.organic.none")) } - } - - @Test - fun install() { - val engine = mockk(relaxed = true) - val store = mockk(relaxed = true) - val extensionInfo = slot() - - ads.install(engine, store) - - verify { ads.installWebExtension(engine, store, capture(extensionInfo)) } - assertEquals(ADS_EXTENSION_ID, extensionInfo.captured.id) - assertEquals(ADS_EXTENSION_RESOURCE_URL, extensionInfo.captured.resourceUrl) - assertEquals(ADS_MESSAGE_ID, extensionInfo.captured.messageId) - } - - @Test - fun `process the document urls and reports an ad`() { - val metricEvent = slot() - val first = "https://www.google.com/aclk" - val second = "https://www.google.com/aaa" - val urls = JSONArray() - urls.put(first) - urls.put(second) - val cookies = JSONArray() - val message = JSONObject() - message.put(ADS_MESSAGE_DOCUMENT_URLS_KEY, urls) - message.put(ADS_MESSAGE_SESSION_URL_KEY, "https://www.google.com/search?q=aaa") - message.put(ADS_MESSAGE_COOKIES_KEY, cookies) - - ads.processMessage(message) - - verify { metrics.track(capture(metricEvent)) } - assertEquals(ads.providerList[0].name, metricEvent.captured.label) - } - - @Test - fun `process the document urls and don't find ads`() { - val first = "https://www.google.com/aaaaaa" - val second = "https://www.google.com/aaa" - val urls = JSONArray() - urls.put(first) - urls.put(second) - val cookies = JSONArray() - val message = JSONObject() - message.put(ADS_MESSAGE_DOCUMENT_URLS_KEY, urls) - message.put(ADS_MESSAGE_SESSION_URL_KEY, "https://www.google.com/search?q=aaa") - message.put(ADS_MESSAGE_COOKIES_KEY, cookies) - - ads.processMessage(message) - - verify(exactly = 0) { metrics.track(any()) } - } - - @Test - fun `track bing sap-follow-on metric by cookies`() { - val url = "https://www.bing.com/search?q=aaa&pc=MOZMBA&form=QBRERANDOM" - - ads.cachedCookies = createCookieList() - ads.trackAdClickedMetric(url, listOf("https://www.bing.com/aclik", "https://www.aaa.com")) - - verify { metrics.track(Event.SearchAdClicked("bing.in-content.sap-follow-on.mozmba")) } - } - - private fun createCookieList(): List { - val first = JSONObject() - first.put("name", "SRCHS") - first.put("value", "PC=MOZMBA") - val second = JSONObject() - second.put("name", "RANDOM") - second.put("value", "RANDOM") - return listOf(first, second) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetryTest.kt b/app/src/test/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetryTest.kt deleted file mode 100644 index 72ff256c18..0000000000 --- a/app/src/test/java/org/mozilla/fenix/search/telemetry/incontent/InContentTelemetryTest.kt +++ /dev/null @@ -1,184 +0,0 @@ -package org.mozilla.fenix.search.telemetry.incontent - -import io.mockk.mockk -import io.mockk.slot -import io.mockk.spyk -import io.mockk.verify -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.Engine -import org.json.JSONArray -import org.json.JSONObject -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import org.mozilla.fenix.search.telemetry.ExtensionInfo -import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry.Companion.COOKIES_EXTENSION_ID -import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry.Companion.COOKIES_EXTENSION_RESOURCE_URL -import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry.Companion.COOKIES_MESSAGE_ID -import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry.Companion.COOKIES_MESSAGE_LIST_KEY -import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry.Companion.COOKIES_MESSAGE_SESSION_URL_KEY - -@RunWith(FenixRobolectricTestRunner::class) -class InContentTelemetryTest { - - private val metrics: MetricController = mockk(relaxed = true) - private lateinit var telemetry: InContentTelemetry - - @Before - fun setUp() { - telemetry = spyk(InContentTelemetry(metrics)) - } - - @Test - fun install() { - val engine = mockk(relaxed = true) - val store = mockk(relaxed = true) - val extensionInfo = slot() - - telemetry.install(engine, store) - - verify { telemetry.installWebExtension(engine, store, capture(extensionInfo)) } - Assert.assertEquals(COOKIES_EXTENSION_ID, extensionInfo.captured.id) - Assert.assertEquals(COOKIES_EXTENSION_RESOURCE_URL, extensionInfo.captured.resourceUrl) - Assert.assertEquals(COOKIES_MESSAGE_ID, extensionInfo.captured.messageId) - } - - @Test - fun processMessage() { - val first = JSONObject() - val second = JSONObject() - val array = JSONArray() - array.put(first) - array.put(second) - val message = JSONObject() - val url = "https://www.google.com/search?q=aaa" - message.put(COOKIES_MESSAGE_LIST_KEY, array) - message.put(COOKIES_MESSAGE_SESSION_URL_KEY, url) - - telemetry.processMessage(message) - - verify { telemetry.trackPartnerUrlTypeMetric(url, listOf(first, second)) } - } - - @Test - fun `track google sap metric`() { - val url = "https://www.google.com/search?q=aaa&client=firefox-b-m" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("google.in-content.sap.firefox-b-m")) } - } - - @Test - fun `track duckduckgo sap metric`() { - val url = "https://duckduckgo.com/?q=aaa&t=fpas" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("duckduckgo.in-content.sap.fpas")) } - } - - @Test - fun `track baidu sap metric`() { - val url = "https://m.baidu.com/s?from=1000969a&word=aaa" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("baidu.in-content.sap._1000969a")) } - } - - @Test - fun `track bing sap metric`() { - val url = "https://www.bing.com/search?q=aaa&pc=MOZMBA" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("bing.in-content.sap.mozmba")) } - } - - @Test - fun `track google sap-follow-on metric`() { - val url = "https://www.google.com/search?q=aaa&client=firefox-b-m&oq=random" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("google.in-content.sap-follow-on.firefox-b-m")) } - } - - @Test - fun `track google sap-follow-on and topSite metric`() { - val url = "https://www.google.com/search?q=aaa&client=firefox-b-m&channel=ts&oq=random" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("google.in-content.sap-follow-on.firefox-b-m.ts")) } - } - - @Test - fun `track baidu sap-follow-on metric`() { - val url = "https://m.baidu.com/s?from=1000969a&word=aaa&oq=random" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("baidu.in-content.sap-follow-on._1000969a")) } - } - - @Test - fun `track bing sap-follow-on metric by cookies`() { - val url = "https://www.bing.com/search?q=aaa&pc=MOZMBA&form=QBRERANDOM" - - telemetry.trackPartnerUrlTypeMetric(url, createCookieList()) - - verify { metrics.track(Event.SearchInContent("bing.in-content.sap-follow-on.mozmba")) } - } - - @Test - fun `track google organic metric`() { - val url = "https://www.google.com/search?q=aaa" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("google.in-content.organic.none")) } - } - - @Test - fun `track duckduckgo organic metric`() { - val url = "https://duckduckgo.com/?q=aaa" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("duckduckgo.in-content.organic.none")) } - } - - @Test - fun `track bing organic metric`() { - val url = "https://www.bing.com/search?q=aaa" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("bing.in-content.organic.none")) } - } - - @Test - fun `track baidu organic metric`() { - val url = "https://m.baidu.com/s?word=aaa" - - telemetry.trackPartnerUrlTypeMetric(url, listOf()) - - verify { metrics.track(Event.SearchInContent("baidu.in-content.organic.none")) } - } - - private fun createCookieList(): List { - val first = JSONObject() - first.put("name", "SRCHS") - first.put("value", "PC=MOZMBA") - val second = JSONObject() - second.put("name", "RANDOM") - second.put("value", "RANDOM") - return listOf(first, second) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/telemetry/TelemetryMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/telemetry/TelemetryMiddlewareTest.kt index e73d47280c..aac2910e72 100644 --- a/app/src/test/java/org/mozilla/fenix/telemetry/TelemetryMiddlewareTest.kt +++ b/app/src/test/java/org/mozilla/fenix/telemetry/TelemetryMiddlewareTest.kt @@ -15,7 +15,6 @@ import mozilla.components.browser.state.action.DownloadAction import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState -import mozilla.components.browser.state.state.LoadRequestState import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.service.glean.testing.GleanTestRule @@ -27,8 +26,6 @@ import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -37,7 +34,6 @@ import org.junit.runner.RunWith import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.GleanMetrics.EngineTab as EngineMetrics @@ -49,7 +45,6 @@ class TelemetryMiddlewareTest { private lateinit var settings: Settings private lateinit var telemetryMiddleware: TelemetryMiddleware private lateinit var metrics: MetricController - private lateinit var adsTelemetry: AdsTelemetry private val testDispatcher = TestCoroutineDispatcher() @get:Rule @@ -66,10 +61,8 @@ class TelemetryMiddlewareTest { settings = Settings(testContext) metrics = mockk(relaxed = true) - adsTelemetry = mockk() telemetryMiddleware = TelemetryMiddleware( settings, - adsTelemetry, metrics ) store = BrowserStore( @@ -203,70 +196,6 @@ class TelemetryMiddlewareTest { verify(exactly = 1) { metrics.track(Event.NormalAndPrivateUriOpened) } } - @Test - fun `GIVEN a load request WHEN no redirect chain is available THEN a new chain will be created`() { - val tab = createTab(id = "1", url = "http://mozilla.org") - store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() - store.dispatch(ContentAction.UpdateLoadRequestAction( - tab.id, LoadRequestState(tab.content.url, true, true)) - ).joinBlocking() - - assertNull(telemetryMiddleware.redirectChains[tab.id]) - - store.dispatch(ContentAction.UpdateLoadRequestAction( - tab.id, LoadRequestState("https://mozilla.org", true, true)) - ).joinBlocking() - - assertNotNull(telemetryMiddleware.redirectChains[tab.id]) - assertEquals(tab.content.url, telemetryMiddleware.redirectChains[tab.id]!!.root) - } - - @Test - fun `GIVEN a load request WHEN a redirect chain is available THEN url is added to chain`() { - val tab = createTab(id = "1", url = "http://mozilla.org") - store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() - store.dispatch(ContentAction.UpdateLoadRequestAction( - tab.id, LoadRequestState("https://mozilla.org", true, true)) - ).joinBlocking() - - assertNotNull(telemetryMiddleware.redirectChains[tab.id]) - assertEquals(tab.content.url, telemetryMiddleware.redirectChains[tab.id]!!.root) - assertEquals("https://mozilla.org", telemetryMiddleware.redirectChains[tab.id]!!.chain.first()) - } - - @Test - fun `GIVEN a location update WHEN no redirect chain is available THEN no ads telemetry is recorded`() { - val tab = createTab(id = "1", url = "http://mozilla.org") - store.dispatch(ContentAction.UpdateUrlAction(tab.id, "http://mozilla.org")).joinBlocking() - verify(exactly = 0) { adsTelemetry.trackAdClickedMetric(any(), any()) } - } - - @Test - fun `GIVEN a location update WHEN a redirect chain is available THEN ads telemetry is recorded`() { - val tab = createTab(id = "1", url = "http://mozilla.org") - store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() - store.dispatch(ContentAction.UpdateLoadRequestAction( - tab.id, LoadRequestState("https://mozilla.org", true, true)) - ).joinBlocking() - - store.dispatch(ContentAction.UpdateUrlAction(tab.id, "https://mozilla.org")).joinBlocking() - verify(exactly = 1) { adsTelemetry.trackAdClickedMetric(tab.content.url, listOf("https://mozilla.org")) } - } - - @Test - fun `GIVEN a location update WHEN ads telemetry is recorded THEN redirect chain is reset`() { - val tab = createTab(id = "1", url = "http://mozilla.org") - store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() - store.dispatch(ContentAction.UpdateLoadRequestAction( - tab.id, LoadRequestState("https://mozilla.org", true, true)) - ).joinBlocking() - - assertNotNull(telemetryMiddleware.redirectChains[tab.id]) - - store.dispatch(ContentAction.UpdateUrlAction(tab.id, "https://mozilla.org")).joinBlocking() - assertNull(telemetryMiddleware.redirectChains[tab.id]) - } - @Test fun `WHEN a download is added THEN the downloads count is updated`() { store.dispatch(DownloadAction.AddDownloadAction(mock())).joinBlocking() diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index c5e4c91046..ab526da01d 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "91.0.20210712190108" + const val VERSION = "92.0.20210714133214" }