For #6558 - cleanup + added unit tests

fennec/production
Mihai Branescu 4 years ago committed by Jeff Boek
parent f03d65b13d
commit c4d76dce5a

@ -2007,11 +2007,12 @@ browser.search:
description: >
Records counts of SERP pages with adverts displayed. The key format is <provider-name>.
send_in_pings:
- baseline
- metrics
bugs:
- https://github.com/mozilla-mobile/fenix/issues/6558
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/6558
- https://github.com/mozilla-mobile/fenix/pull/10112
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"
@ -2020,11 +2021,12 @@ browser.search:
description: >
Records clicks of adverts on SERP pages. The key format is <provider-name>.
send_in_pings:
- baseline
- metrics
bugs:
- https://github.com/mozilla-mobile/fenix/issues/6558
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/6558
- https://github.com/mozilla-mobile/fenix/pull/10112
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"

@ -1,4 +1,10 @@
function collect_urls(urls) {
/* 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) {
@ -7,11 +13,30 @@ function collect_urls(urls) {
urls.push(anchor.href);
}
}
let urls = [];
collect_urls(urls)
let message = {
'url': document.location.href,
'urls': urls
function sendLinks() {
let urls = [];
collectLinks(urls);
let message = {
'url': document.location.href,
'urls': urls
};
browser.runtime.sendNativeMessage("MozacBrowserAds", message);
}
browser.runtime.sendNativeMessage("MozacBrowserAds", message);
var timeout;
window.onload = function() {
timeout = setTimeout(sendLinks, ADLINK_CHECK_TIMEOUT_MS);
};
window.onpageshow = function(event) {
if (event.persisted) {
timeout = setTimeout(sendLinks, ADLINK_CHECK_TIMEOUT_MS);
}
};
window.onunload = function() {
clearTimeout(timeout);
};

@ -1,10 +0,0 @@
package org.mozilla.fenix.ads
data class SearchProviderCookie(
val extraCodeParam: String,
val extraCodePrefixes: List<String>,
val host: String,
val name: String,
val codeParam: String,
val codePrefixes: List<String>
)

@ -1,10 +1,14 @@
/* 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.browser
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.session.Session
import org.mozilla.fenix.ads.AdsTelemetry
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
class TelemetrySessionObserver(
private val metrics: MetricController,

@ -9,7 +9,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import org.mozilla.fenix.ads.AdsTelemetry
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics

@ -42,7 +42,7 @@ import org.mozilla.fenix.AppRequestInterceptor
import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ads.AdsTelemetry
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.media.MediaService

@ -1,6 +1,10 @@
/* 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.ext
import org.mozilla.fenix.ads.SearchProviderModel
import org.mozilla.fenix.search.telemetry.SearchProviderModel
fun SearchProviderModel.containsAds(urlList: List<String>): Boolean {
return urlList.containsAds(this.extraAdServersRegexps)

@ -1,12 +0,0 @@
package org.mozilla.fenix.ext
import org.json.JSONArray
@Suppress("UNCHECKED_CAST")
fun <T> JSONArray.toList(): List<T> {
val result = ArrayList<T>()
for (i in 0 until length()) {
result.add(get(i) as T)
}
return result
}

@ -0,0 +1,14 @@
/* 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<String>,
val host: String,
val name: String,
val codeParam: String,
val codePrefixes: List<String>
)

@ -1,4 +1,8 @@
package org.mozilla.fenix.ads
/* 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,

@ -1,5 +1,10 @@
package org.mozilla.fenix.ads
/* 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
@ -11,16 +16,19 @@ 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
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.containsAds
import org.mozilla.fenix.ext.toList
import org.mozilla.fenix.search.telemetry.SearchProviderCookie
import org.mozilla.fenix.search.telemetry.SearchProviderModel
class AdsTelemetry(private val metrics: MetricController) {
private val providerList = listOf(
@VisibleForTesting
internal val providerList = listOf(
SearchProviderModel(
name = "google",
regexp = "^https:\\/\\/www\\.google\\.(?:.+)\\/search",
@ -38,7 +46,8 @@ class AdsTelemetry(private val metrics: MetricController) {
codePrefixes = listOf("ff"),
followOnParams = listOf("oq", "ved", "ei"),
extraAdServersRegexps = listOf(
"^https:\\/\\/duckduckgo.com\\/y\\.js"
"^https:\\/\\/duckduckgo.com\\/y\\.js",
"^https:\\/\\/www\\.amazon\\.(?:[a-z.]{2,24}).*(?:tag=duckduckgo-)"
)
),
SearchProviderModel(
@ -48,7 +57,7 @@ class AdsTelemetry(private val metrics: MetricController) {
),
SearchProviderModel(
name = "baidu",
regexp = "^https:\\/\\/www\\.baidu\\.com\\/(?:s|baidu)",
regexp = "^https:\\/\\/www\\.baidu\\.com\\/from=844b\\/(?:s|baidu)",
queryParam = "wd",
codeParam = "tn",
codePrefixes = listOf("34046034_", "monline_"),
@ -108,7 +117,8 @@ class AdsTelemetry(private val metrics: MetricController) {
}
}
private fun getProviderForUrl(url: String): SearchProviderModel? {
@VisibleForTesting
internal fun getProviderForUrl(url: String): SearchProviderModel? {
for (provider in providerList) {
if (Regex(provider.regexp).containsMatchIn(url)) {
return provider
@ -138,7 +148,8 @@ class AdsTelemetry(private val metrics: MetricController) {
}
}
private inner class AdsTelemetryContentMessageHandler : MessageHandler {
@VisibleForTesting
internal inner class AdsTelemetryContentMessageHandler : MessageHandler {
override fun onMessage(message: Any, source: EngineSession?): Any? {
if (message is JSONObject) {
@ -164,10 +175,14 @@ class AdsTelemetry(private val metrics: MetricController) {
}
companion object {
private const val ADS_EXTENSION_ID = "mozacBrowserAds"
private const val ADS_EXTENSION_RESOURCE_URL = "resource://android/assets/extensions/ads/"
@VisibleForTesting
internal const val ADS_EXTENSION_ID = "mozacBrowserAds"
@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"
private const val ADS_MESSAGE_ID = "MozacBrowserAds"
private const val ADS_MESSAGE_SESSION_URL_KEY = "url"
private const val ADS_MESSAGE_DOCUMENT_URLS_KEY = "urls"
}
}

@ -0,0 +1,120 @@
/* 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.browser
import androidx.lifecycle.LifecycleOwner
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
class TelemetrySessionObserverTest {
private val owner: LifecycleOwner = mockk(relaxed = true)
private val sessionManager: SessionManager = mockk(relaxed = true)
private val metrics: MetricController = mockk(relaxed = true)
private val ads: AdsTelemetry = mockk(relaxed = true)
private lateinit var singleSessionObserver: TelemetrySessionObserver
@Before
fun setup() {
singleSessionObserver =
UriOpenedObserver(owner, sessionManager, metrics, ads).singleSessionObserver
}
@Test
fun `tracks that a url was loaded`() {
val session: Session = mockk(relaxed = true)
every { session.url } returns "https://mozilla.com"
singleSessionObserver.onLoadingStateChanged(session, loading = false)
verify(exactly = 0) { metrics.track(Event.UriOpened) }
singleSessionObserver.onLoadingStateChanged(session, loading = true)
singleSessionObserver.onLoadingStateChanged(session, loading = false)
verify { metrics.track(Event.UriOpened) }
}
@Test
fun `add originSessionUrl on first link of redirect chain and start chain`() {
val session: Session = mockk(relaxed = true)
val sessionUrl = "https://www.google.com/search"
val url = "www.aaa.com"
every { session.url } returns sessionUrl
singleSessionObserver.onLoadRequest(
session,
url,
triggeredByRedirect = false,
triggeredByWebContent = false
)
Assert.assertEquals(sessionUrl, singleSessionObserver.originSessionUrl)
Assert.assertEquals(url, singleSessionObserver.redirectChain[0])
}
@Test
fun `add to redirect chain on subsequent onLoadRequests`() {
val session: Session = mockk(relaxed = true)
val url = "https://www.google.com/search"
val newUrl = "www.aaa.com"
every { session.url } returns url
singleSessionObserver.originSessionUrl = url
singleSessionObserver.redirectChain.add(url)
singleSessionObserver.onLoadRequest(
session,
newUrl,
triggeredByRedirect = false,
triggeredByWebContent = false
)
Assert.assertEquals(url, singleSessionObserver.originSessionUrl)
Assert.assertEquals(url, singleSessionObserver.redirectChain[0])
Assert.assertEquals(newUrl, singleSessionObserver.redirectChain[1])
}
@Test
fun `do nothing onLoadRequest when it's the first url of the session`() {
val session: Session = mockk(relaxed = true)
val url = "https://www.google.com/search"
every { session.url } returns url
singleSessionObserver.onLoadRequest(
session,
url,
triggeredByRedirect = false,
triggeredByWebContent = false
)
Assert.assertNull(singleSessionObserver.originSessionUrl)
Assert.assertEquals(0, singleSessionObserver.redirectChain.size)
}
@Test
fun `check if metric for ad clicked should be sent`() {
val session: Session = mockk(relaxed = true)
val sessionUrl = "doesn't matter"
val originSessionUrl = "https://www.google.com/search"
val url = "www.aaa.com"
every { session.url } returns sessionUrl
val redirectChain = mutableListOf(url)
singleSessionObserver.redirectChain = redirectChain
singleSessionObserver.originSessionUrl = originSessionUrl
singleSessionObserver.onUrlChanged(session, url)
verify {
ads.trackAdClickedMetric(
originSessionUrl,
redirectChain
)
}
Assert.assertNull(singleSessionObserver.originSessionUrl)
Assert.assertEquals(0, singleSessionObserver.redirectChain.size)
}
}

@ -4,42 +4,36 @@
package org.mozilla.fenix.browser
import android.content.Context
import androidx.lifecycle.LifecycleOwner
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.components.metrics.MetricController
class UriOpenedObserverTest {
private lateinit var context: Context
private lateinit var owner: LifecycleOwner
private lateinit var sessionManager: SessionManager
private lateinit var metrics: MetricController
private val owner: LifecycleOwner = mockk(relaxed = true)
private val sessionManager: SessionManager = mockk(relaxed = true)
private val metrics: MetricController = mockk()
private val ads: AdsTelemetry = mockk()
private lateinit var observer: UriOpenedObserver
@Before
fun setup() {
context = mockk(relaxed = true)
owner = mockk(relaxed = true)
sessionManager = mockk(relaxed = true)
metrics = mockk(relaxed = true)
observer = UriOpenedObserver(owner, sessionManager, metrics, ads)
}
@Test
fun `registers self as observer`() {
val observer = UriOpenedObserver(context, owner, sessionManager, metrics)
verify { sessionManager.register(observer, owner) }
}
@Test
fun `registers single session observer`() {
val observer = UriOpenedObserver(context, owner, sessionManager, metrics)
val session: Session = mockk(relaxed = true)
observer.onSessionAdded(session)
@ -48,18 +42,4 @@ class UriOpenedObserverTest {
observer.onSessionRemoved(session)
verify { session.unregister(observer.singleSessionObserver) }
}
@Test
fun `tracks that a url was loaded`() {
val observer = UriOpenedObserver(context, owner, sessionManager, metrics).singleSessionObserver
val session: Session = mockk(relaxed = true)
every { session.url } returns "https://mozilla.com"
observer.onLoadingStateChanged(session, loading = false)
verify(exactly = 0) { metrics.track(Event.UriOpened) }
observer.onLoadingStateChanged(session, loading = true)
observer.onLoadingStateChanged(session, loading = false)
verify { metrics.track(Event.UriOpened) }
}
}

@ -0,0 +1,41 @@
/* 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.ext
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mozilla.fenix.search.telemetry.SearchProviderModel
class AdsTest {
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.containsAds(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.containsAds(listOf(nonAd1, nonAd2)))
}
}

@ -0,0 +1,128 @@
/* 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.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_DOCUMENT_URLS_KEY
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
private lateinit var adsMessageHandler: AdsTelemetry.AdsTelemetryContentMessageHandler
@Before
fun setUp() {
ads = spyk(AdsTelemetry(metrics))
adsMessageHandler = ads.AdsTelemetryContentMessageHandler()
}
@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 metricEvent = slot<Event.SearchAdClicked>()
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(capture(metricEvent)) }
assertEquals(ads.providerList[0].name, metricEvent.captured.label)
}
@Test
fun install() {
val engine = mockk<Engine>(relaxed = true)
val store = mockk<BrowserStore>(relaxed = true)
ads.install(engine, store)
verify {
engine.installWebExtension(
id = ADS_EXTENSION_ID,
url = ADS_EXTENSION_RESOURCE_URL,
allowContentMessaging = true,
onSuccess = any(),
onError = any()
)
}
}
@Test
fun `message handler processes the document urls and reports an ad`() {
val metricEvent = slot<Event.SearchWithAds>()
val first = "https://www.google.com/aclk"
val second = "https://www.google.com/aaa"
val array = JSONArray()
array.put(first)
array.put(second)
val message = JSONObject()
message.put(ADS_MESSAGE_DOCUMENT_URLS_KEY, array)
message.put(ADS_MESSAGE_SESSION_URL_KEY, "https://www.google.com/search?q=aaa")
assertEquals("", adsMessageHandler.onMessage(message, mockk()))
verify { metrics.track(capture(metricEvent)) }
assertEquals(ads.providerList[0].name, metricEvent.captured.label)
}
@Test
fun `message handler processes the document urls and doesn't find ads`() {
val first = "https://www.google.com/aaaaaa"
val second = "https://www.google.com/aaa"
val array = JSONArray()
array.put(first)
array.put(second)
val message = JSONObject()
message.put(ADS_MESSAGE_DOCUMENT_URLS_KEY, array)
message.put(ADS_MESSAGE_SESSION_URL_KEY, "https://www.google.com/search?q=aaa")
assertEquals("", adsMessageHandler.onMessage(message, mockk()))
verify(exactly = 0) { metrics.track(any()) }
}
@Test(expected = IllegalStateException::class)
fun `message handler finds no json object`() {
val message = "message"
adsMessageHandler.onMessage(message, mockk())
}
}

@ -46,8 +46,8 @@ The following metrics are added to the ping:
| Name | Type | Description | Data reviews | Extras | Expiration |
| --- | --- | --- | --- | --- | --- |
| browser.search.ad_clicks |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Records clicks of adverts on SERP pages. The key format is <engine-name>. |[1](https://github.com/mozilla-mobile/fenix/issues/6558)||2020-09-01 |
| browser.search.with_ads |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Records counts of SERP pages with adverts displayed. The key format is <engine-name>. |[1](https://github.com/mozilla-mobile/fenix/issues/6558)||2020-09-01 |
| browser.search.ad_clicks |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Records clicks of adverts on SERP pages. The key format is <provider-name>. |[1](https://github.com/mozilla-mobile/fenix/pull/10112)||2020-09-01 |
| browser.search.with_ads |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Records counts of SERP pages with adverts displayed. The key format is <provider-name>. |[1](https://github.com/mozilla-mobile/fenix/pull/10112)||2020-09-01 |
| events.total_uri_count |[counter](https://mozilla.github.io/glean/book/user/metrics/counter.html) |A counter of URIs visited by the user in the current session, including page reloads. This does not include background page requests and URIs from embedded pages or private browsing. |[1](https://github.com/mozilla-mobile/fenix/pull/1785), [2](https://github.com/mozilla-mobile/fenix/pull/8314)||2020-09-01 |
| metrics.search_count |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |The labels for this counter are `<search-engine-name>.<source>`. If the search engine is bundled with Fenix `search-engine-name` will be the name of the search engine. If it's a custom search engine (defined: https://github.com/mozilla-mobile/fenix/issues/1607) the value will be `custom`. `source` will be: `action`, `suggestion`, `widget` or `shortcut` (depending on the source from which the search started). Also added the `other` option for the source but it should never enter on this case. |[1](https://github.com/mozilla-mobile/fenix/pull/1677), [2](https://github.com/mozilla-mobile/fenix/pull/5216), [3](https://github.com/mozilla-mobile/fenix/pull/7310)||2020-09-01 |
@ -234,6 +234,8 @@ The following metrics are added to the ping:
| --- | --- | --- | --- | --- | --- |
| addons.has_enabled_addons |[boolean](https://mozilla.github.io/glean/book/user/metrics/boolean.html) |Whether or not the user has enabled add-ons on the device. |[1](https://github.com/mozilla-mobile/fenix/pull/8318)||2020-09-01 |
| addons.has_installed_addons |[boolean](https://mozilla.github.io/glean/book/user/metrics/boolean.html) |Whether or not the user has installed add-ons on the device. |[1](https://github.com/mozilla-mobile/fenix/pull/8318)||2020-09-01 |
| browser.search.ad_clicks |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Records clicks of adverts on SERP pages. The key format is <provider-name>. |[1](https://github.com/mozilla-mobile/fenix/pull/10112)||2020-09-01 |
| browser.search.with_ads |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Records counts of SERP pages with adverts displayed. The key format is <provider-name>. |[1](https://github.com/mozilla-mobile/fenix/pull/10112)||2020-09-01 |
| events.total_uri_count |[counter](https://mozilla.github.io/glean/book/user/metrics/counter.html) |A counter of URIs visited by the user in the current session, including page reloads. This does not include background page requests and URIs from embedded pages or private browsing. |[1](https://github.com/mozilla-mobile/fenix/pull/1785), [2](https://github.com/mozilla-mobile/fenix/pull/8314)||2020-09-01 |
| metrics.adjust_ad_group |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |A string containing the Adjust ad group ID from which the user installed Fenix. This will not send on the first session the user runs. If the install is organic, this will be empty. |[1](https://github.com/mozilla-mobile/fenix/pull/9253)||2020-09-01 |
| metrics.adjust_campaign |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |A string containing the Adjust campaign ID from which the user installed Fenix. This will not send on the first session the user runs. If the install is organic, this will be empty. |[1](https://github.com/mozilla-mobile/fenix/pull/5579)||2020-09-01 |

Loading…
Cancel
Save