mirror of
https://github.com/fork-maintainers/iceraven-browser
synced 2024-11-03 23:15:31 +00:00
Closes #11286: Add TelemetryMiddleware to remove Session[Manager] observers
This commit is contained in:
parent
412b412d5f
commit
c852301c93
@ -38,7 +38,6 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.browser.state.state.WebExtensionState
|
||||
@ -62,7 +61,6 @@ import mozilla.components.support.webextensions.WebExtensionPopupFeature
|
||||
import org.mozilla.fenix.GleanMetrics.Metrics
|
||||
import org.mozilla.fenix.addons.AddonDetailsFragmentDirections
|
||||
import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
|
||||
import org.mozilla.fenix.browser.UriOpenedObserver
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
||||
@ -123,7 +121,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||
private var webExtScope: CoroutineScope? = null
|
||||
lateinit var themeManager: ThemeManager
|
||||
lateinit var browsingModeManager: BrowsingModeManager
|
||||
private lateinit var sessionObserver: SessionManager.Observer
|
||||
|
||||
private var isVisuallyComplete = false
|
||||
|
||||
@ -183,8 +180,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||
.attachViewToRunVisualCompletenessQueueLater(WeakReference(rootContainer))
|
||||
}
|
||||
|
||||
sessionObserver = UriOpenedObserver(this)
|
||||
|
||||
checkPrivateShortcutEntryPoint(intent)
|
||||
privateNotificationObserver = PrivateNotificationFeature(
|
||||
applicationContext,
|
||||
|
106
app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt
Normal file
106
app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt
Normal file
@ -0,0 +1,106 @@
|
||||
/* 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
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import mozilla.components.browser.state.action.BrowserAction
|
||||
import mozilla.components.browser.state.action.ContentAction
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
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
|
||||
|
||||
/**
|
||||
* [Middleware] to record telemetry in response to [BrowserAction]s.
|
||||
*
|
||||
* @property settings reference to the application [Settings].
|
||||
* @property adsTelemetry reference to [AdsTelemetry] use to record search telemetry.
|
||||
* @property metrics reference to the configured [MetricController] to record general page load events.
|
||||
*/
|
||||
class TelemetryMiddleware(
|
||||
private val settings: Settings,
|
||||
private val adsTelemetry: AdsTelemetry,
|
||||
private val metrics: MetricController
|
||||
) : Middleware<BrowserState, BrowserAction> {
|
||||
|
||||
private val logger = Logger("TelemetryMiddleware")
|
||||
|
||||
@VisibleForTesting
|
||||
internal val redirectChains = mutableMapOf<String, RedirectChain>()
|
||||
|
||||
/**
|
||||
* Utility to collect URLs / load requests in between location changes.
|
||||
*/
|
||||
internal class RedirectChain(internal val root: String) {
|
||||
internal val chain = mutableListOf<String>()
|
||||
|
||||
fun add(url: String) {
|
||||
chain.add(url)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<BrowserState, BrowserAction>,
|
||||
next: (BrowserAction) -> Unit,
|
||||
action: BrowserAction
|
||||
) {
|
||||
// Pre process actions
|
||||
when (action) {
|
||||
is ContentAction.UpdateLoadingStateAction -> {
|
||||
context.state.findTab(action.sessionId)?.let { tab ->
|
||||
// Record UriOpened event when a non-private page finishes loading
|
||||
if (tab.content.loading && !action.loading && !tab.content.private) {
|
||||
metrics.track(Event.UriOpened)
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next(action)
|
||||
|
||||
// Post process actions
|
||||
when (action) {
|
||||
is TabListAction.AddTabAction,
|
||||
is TabListAction.AddMultipleTabsAction,
|
||||
is TabListAction.RemoveTabAction,
|
||||
is TabListAction.RemoveAllNormalTabsAction,
|
||||
is TabListAction.RemoveAllTabsAction,
|
||||
is TabListAction.RestoreAction -> {
|
||||
// Update/Persist tabs count whenever it changes
|
||||
settings.openTabsCount = context.state.normalTabs.count()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +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.browser
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import mozilla.components.browser.session.Session
|
||||
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,
|
||||
private val ads: AdsTelemetry
|
||||
) : Session.Observer {
|
||||
private var urlLoading: String? = null
|
||||
@VisibleForTesting
|
||||
var redirectChain = mutableListOf<String>()
|
||||
@VisibleForTesting
|
||||
var originSessionUrl: String? = null
|
||||
|
||||
private val temporaryFix = TemporaryFix()
|
||||
|
||||
override fun onLoadingStateChanged(session: Session, loading: Boolean) {
|
||||
if (loading) {
|
||||
urlLoading = session.url
|
||||
} else if (urlLoading != null && !session.private && temporaryFix.shouldSendEvent(session.url)) {
|
||||
temporaryFix.eventSentFor = session.url
|
||||
metrics.track(Event.UriOpened)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a link is clicked, record its redirect chain as well as origin url
|
||||
*/
|
||||
override fun onLoadRequest(
|
||||
session: Session,
|
||||
url: String,
|
||||
triggeredByRedirect: Boolean,
|
||||
triggeredByWebContent: Boolean
|
||||
) {
|
||||
if (isFirstLinkInRedirectChain(url, session.url)) {
|
||||
originSessionUrl = session.url
|
||||
}
|
||||
if (canStartChain()) {
|
||||
redirectChain.add(url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun canStartChain(): Boolean {
|
||||
return originSessionUrl != null
|
||||
}
|
||||
|
||||
private fun isFirstLinkInRedirectChain(url: String, sessionUrl: String): Boolean {
|
||||
return originSessionUrl == null && url != sessionUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* After the redirect chain has finished, check if we encountered an ad on the way and clear
|
||||
* the stored info for that chain
|
||||
*/
|
||||
override fun onUrlChanged(session: Session, url: String) {
|
||||
ads.trackAdClickedMetric(originSessionUrl, redirectChain)
|
||||
originSessionUrl = null
|
||||
redirectChain.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, [Session.Observer.onLoadingStateChanged] is called multiple times the first
|
||||
* time a new session loads a page. This is inflating our telemetry numbers, so we need to
|
||||
* handle it, but we will be able to remove this code when [onLoadingStateChanged] has
|
||||
* been fixed.
|
||||
*
|
||||
* See Fenix #3676
|
||||
* See AC https://github.com/mozilla-mobile/android-components/issues/4795
|
||||
* TODO remove this class after AC #4795 has been fixed
|
||||
*/
|
||||
private class TemporaryFix {
|
||||
var eventSentFor: String? = null
|
||||
|
||||
fun shouldSendEvent(newUrl: String): Boolean = eventSentFor != newUrl
|
||||
}
|
||||
}
|
@ -1,75 +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.browser
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
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.components.metrics.MetricController
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.metrics
|
||||
import org.mozilla.fenix.ext.sessionsOfType
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class UriOpenedObserver(
|
||||
private val settings: Settings,
|
||||
private val owner: LifecycleOwner,
|
||||
private val sessionManager: SessionManager,
|
||||
metrics: MetricController,
|
||||
ads: AdsTelemetry
|
||||
) : SessionManager.Observer {
|
||||
|
||||
constructor(activity: FragmentActivity) : this(
|
||||
activity.applicationContext.settings(),
|
||||
activity,
|
||||
activity.components.core.sessionManager,
|
||||
activity.metrics,
|
||||
activity.components.core.adsTelemetry
|
||||
)
|
||||
|
||||
@VisibleForTesting
|
||||
internal val singleSessionObserver = TelemetrySessionObserver(metrics, ads)
|
||||
|
||||
init {
|
||||
sessionManager.register(this, owner)
|
||||
sessionManager.selectedSession?.register(singleSessionObserver, owner)
|
||||
}
|
||||
|
||||
override fun onSessionSelected(session: Session) {
|
||||
session.register(singleSessionObserver, owner)
|
||||
}
|
||||
|
||||
private fun saveOpenTabsCount() {
|
||||
settings.openTabsCount = sessionManager.sessionsOfType(private = false).count()
|
||||
}
|
||||
|
||||
override fun onAllSessionsRemoved() {
|
||||
saveOpenTabsCount()
|
||||
sessionManager.sessions.forEach {
|
||||
it.unregister(singleSessionObserver)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionAdded(session: Session) {
|
||||
saveOpenTabsCount()
|
||||
session.register(singleSessionObserver, owner)
|
||||
}
|
||||
|
||||
override fun onSessionRemoved(session: Session) {
|
||||
saveOpenTabsCount()
|
||||
session.unregister(singleSessionObserver)
|
||||
}
|
||||
|
||||
override fun onSessionsRestored() {
|
||||
saveOpenTabsCount()
|
||||
sessionManager.sessions.forEach {
|
||||
it.register(singleSessionObserver, owner)
|
||||
}
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ import org.mozilla.fenix.Config
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.StrictModeManager
|
||||
import org.mozilla.fenix.TelemetryMiddleware
|
||||
import org.mozilla.fenix.downloads.DownloadService
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.settings
|
||||
@ -154,6 +155,11 @@ class Core(
|
||||
MediaMiddleware(context, MediaService::class.java),
|
||||
DownloadMiddleware(context, DownloadService::class.java),
|
||||
ReaderViewMiddleware(),
|
||||
TelemetryMiddleware(
|
||||
context.settings(),
|
||||
adsTelemetry,
|
||||
metrics
|
||||
),
|
||||
ThumbnailsMiddleware(thumbnailStorage),
|
||||
UndoMiddleware(::lookupSessionManager, context.getUndoDelay())
|
||||
) + EngineMiddleware.create(engine, ::findSessionById)
|
||||
@ -250,12 +256,16 @@ class Core(
|
||||
BrowserIcons(context, client)
|
||||
}
|
||||
|
||||
val metrics by lazy {
|
||||
context.components.analytics.metrics
|
||||
}
|
||||
|
||||
val adsTelemetry by lazy {
|
||||
AdsTelemetry(context.components.analytics.metrics)
|
||||
AdsTelemetry(metrics)
|
||||
}
|
||||
|
||||
val searchTelemetry by lazy {
|
||||
InContentTelemetry(context.components.analytics.metrics)
|
||||
InContentTelemetry(metrics)
|
||||
}
|
||||
|
||||
/**
|
||||
|
219
app/src/test/java/org/mozilla/fenix/TelemetryMiddlewareTest.kt
Normal file
219
app/src/test/java/org/mozilla/fenix/TelemetryMiddlewareTest.kt
Normal file
@ -0,0 +1,219 @@
|
||||
/* 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
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.state.action.ContentAction
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.state.LoadRequestState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
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
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class TelemetryMiddlewareTest {
|
||||
|
||||
private lateinit var store: BrowserStore
|
||||
private lateinit var settings: Settings
|
||||
private lateinit var telemetryMiddleware: TelemetryMiddleware
|
||||
private lateinit var metrics: MetricController
|
||||
private lateinit var adsTelemetry: AdsTelemetry
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
settings = Settings(testContext)
|
||||
metrics = mockk()
|
||||
adsTelemetry = mockk()
|
||||
telemetryMiddleware = TelemetryMiddleware(
|
||||
settings,
|
||||
adsTelemetry,
|
||||
metrics
|
||||
)
|
||||
store = BrowserStore(middleware = listOf(telemetryMiddleware))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN a tab is added THEN the open tab count is updated`() {
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
|
||||
store.dispatch(TabListAction.AddTabAction(createTab("https://mozilla.org"))).joinBlocking()
|
||||
assertEquals(1, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN a private tab is added THEN the open tab count is not updated`() {
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
|
||||
store.dispatch(TabListAction.AddTabAction(createTab("https://mozilla.org", private = true))).joinBlocking()
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN multiple tabs are added THEN the open tab count is updated`() {
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
store.dispatch(
|
||||
TabListAction.AddMultipleTabsAction(listOf(
|
||||
createTab("https://mozilla.org"),
|
||||
createTab("https://firefox.com"))
|
||||
)
|
||||
).joinBlocking()
|
||||
|
||||
assertEquals(2, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN a tab is removed THEN the open tab count is updated`() {
|
||||
store.dispatch(
|
||||
TabListAction.AddMultipleTabsAction(listOf(
|
||||
createTab(id = "1", url = "https://mozilla.org"),
|
||||
createTab(id = "2", url = "https://firefox.com"))
|
||||
)
|
||||
).joinBlocking()
|
||||
assertEquals(2, settings.openTabsCount)
|
||||
|
||||
store.dispatch(TabListAction.RemoveTabAction("1")).joinBlocking()
|
||||
assertEquals(1, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN all tabs are removed THEN the open tab count is updated`() {
|
||||
store.dispatch(
|
||||
TabListAction.AddMultipleTabsAction(listOf(
|
||||
createTab("https://mozilla.org"),
|
||||
createTab("https://firefox.com"))
|
||||
)
|
||||
).joinBlocking()
|
||||
assertEquals(2, settings.openTabsCount)
|
||||
|
||||
store.dispatch(TabListAction.RemoveAllTabsAction).joinBlocking()
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN all normal tabs are removed THEN the open tab count is updated`() {
|
||||
store.dispatch(
|
||||
TabListAction.AddMultipleTabsAction(listOf(
|
||||
createTab("https://mozilla.org"),
|
||||
createTab("https://firefox.com"),
|
||||
createTab("https://getpocket.com", private = true))
|
||||
)
|
||||
).joinBlocking()
|
||||
assertEquals(2, settings.openTabsCount)
|
||||
|
||||
store.dispatch(TabListAction.RemoveAllNormalTabsAction).joinBlocking()
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN tabs are restored THEN the open tab count is updated`() {
|
||||
assertEquals(0, settings.openTabsCount)
|
||||
val tabsToRestore = listOf(
|
||||
createTab("https://mozilla.org"),
|
||||
createTab("https://firefox.com")
|
||||
)
|
||||
|
||||
store.dispatch(TabListAction.RestoreAction(tabsToRestore)).joinBlocking()
|
||||
assertEquals(2, settings.openTabsCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a page is loading WHEN loading is complete THEN we record a UriOpened event`() {
|
||||
val tab = createTab(id = "1", url = "https://mozilla.org")
|
||||
store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking()
|
||||
store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, true)).joinBlocking()
|
||||
verify(exactly = 0) { metrics.track(Event.UriOpened) }
|
||||
|
||||
store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, false)).joinBlocking()
|
||||
verify(exactly = 1) { metrics.track(Event.UriOpened) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a private page is loading WHEN loading is complete THEN we never record a UriOpened event`() {
|
||||
val tab = createTab(id = "1", url = "https://mozilla.org", private = true)
|
||||
store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking()
|
||||
store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, true)).joinBlocking()
|
||||
verify(exactly = 0) { metrics.track(Event.UriOpened) }
|
||||
|
||||
store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, false)).joinBlocking()
|
||||
verify(exactly = 0) { metrics.track(Event.UriOpened) }
|
||||
}
|
||||
|
||||
@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])
|
||||
}
|
||||
}
|
@ -1,123 +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.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.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
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
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class TelemetrySessionObserverTest {
|
||||
|
||||
private val settings: Settings = mockk(relaxed = true)
|
||||
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(settings, 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
|
||||
)
|
||||
assertEquals(sessionUrl, singleSessionObserver.originSessionUrl)
|
||||
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
|
||||
)
|
||||
assertEquals(url, singleSessionObserver.originSessionUrl)
|
||||
assertEquals(url, singleSessionObserver.redirectChain[0])
|
||||
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
|
||||
)
|
||||
assertNull(singleSessionObserver.originSessionUrl)
|
||||
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
|
||||
)
|
||||
}
|
||||
assertNull(singleSessionObserver.originSessionUrl)
|
||||
assertEquals(0, singleSessionObserver.redirectChain.size)
|
||||
}
|
||||
}
|
@ -1,88 +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.browser
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
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.MetricController
|
||||
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class UriOpenedObserverTest {
|
||||
|
||||
@MockK(relaxed = true) private lateinit var settings: Settings
|
||||
@MockK(relaxed = true) private lateinit var owner: LifecycleOwner
|
||||
@MockK(relaxed = true) private lateinit var sessionManager: SessionManager
|
||||
@MockK private lateinit var metrics: MetricController
|
||||
@MockK private lateinit var ads: AdsTelemetry
|
||||
private lateinit var observer: UriOpenedObserver
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
observer = UriOpenedObserver(settings, owner, sessionManager, metrics, ads)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registers self as observer`() {
|
||||
verify { sessionManager.register(observer, owner) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registers single session observer`() {
|
||||
every { sessionManager.sessions } returns listOf(
|
||||
mockk {
|
||||
every { private } returns false
|
||||
},
|
||||
mockk {
|
||||
every { private } returns true
|
||||
}
|
||||
)
|
||||
val session: Session = mockk(relaxed = true)
|
||||
|
||||
observer.onSessionAdded(session)
|
||||
verify { session.register(observer.singleSessionObserver, owner) }
|
||||
|
||||
observer.onSessionSelected(session)
|
||||
verify { session.register(observer.singleSessionObserver, owner) }
|
||||
|
||||
observer.onSessionRemoved(session)
|
||||
verify { session.unregister(observer.singleSessionObserver) }
|
||||
|
||||
verify { settings.openTabsCount = 1 }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registers when all sessions are restored`() {
|
||||
val session1: Session = mockk(relaxed = true)
|
||||
val session2: Session = mockk(relaxed = true)
|
||||
every { sessionManager.sessions } returns listOf(session1, session2)
|
||||
|
||||
observer.onSessionsRestored()
|
||||
|
||||
verify { session1.register(observer.singleSessionObserver, owner) }
|
||||
verify { session2.register(observer.singleSessionObserver, owner) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregisters when all sessions are removed`() {
|
||||
val session1: Session = mockk(relaxed = true)
|
||||
val session2: Session = mockk(relaxed = true)
|
||||
every { sessionManager.sessions } returns listOf(session1, session2)
|
||||
|
||||
observer.onAllSessionsRemoved()
|
||||
|
||||
verify { session1.unregister(observer.singleSessionObserver) }
|
||||
verify { session2.unregister(observer.singleSessionObserver) }
|
||||
}
|
||||
}
|
@ -3,5 +3,5 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
object AndroidComponents {
|
||||
const val VERSION = "63.0.20201014190200"
|
||||
const val VERSION = "63.0.20201015143124"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user