Bug 1839239 - Add crash bread crumbs for dialogs.

fenix/117.0
Arturo Mejia 11 months ago committed by mergify[bot]
parent 920e7c4131
commit 20f3002e80

@ -442,6 +442,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
private fun startMetricsIfEnabled() {
if (settings().isTelemetryEnabled) {
components.analytics.metrics.start(MetricServiceType.Data)
components.analytics.crashFactCollector.start()
}
if (settings().isMarketingTelemetryEnabled) {

@ -19,7 +19,9 @@ import androidx.appcompat.view.ContextThemeWrapper
import com.google.android.material.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import mozilla.components.concept.base.crash.Breadcrumb
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.ext.components
/**
* Base [AppCompatDialogFragment] that adds behaviour to create a top or bottom dialog.
@ -36,6 +38,9 @@ abstract class FenixDialogFragment : AppCompatDialogFragment() {
abstract val layoutId: Int
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("FenixDialogFragment onCreateDialog Gravity $gravity"),
)
return if (gravity == Gravity.BOTTOM) {
BottomSheetDialog(requireContext(), this.theme).apply {
setOnShowListener {

@ -57,6 +57,7 @@ import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.concept.engine.permission.SitePermissions
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.accounts.FxaCapability
@ -550,6 +551,9 @@ abstract class BaseBrowserFragment :
customFirstPartyDownloadDialog = { filename, contentSize, positiveAction, negativeAction ->
run {
if (currentStartDownloadDialog == null) {
context.components.analytics.crashReporter.recordCrashBreadcrumb(
Breadcrumb("FirstPartyDownloadDialog created"),
)
FirstPartyDownloadDialog(
activity = requireActivity(),
filename = filename.value,
@ -557,6 +561,9 @@ abstract class BaseBrowserFragment :
positiveButtonAction = positiveAction.value,
negativeButtonAction = negativeAction.value,
).onDismiss {
context.components.analytics.crashReporter.recordCrashBreadcrumb(
Breadcrumb("FirstPartyDownloadDialog onDismiss"),
)
currentStartDownloadDialog = null
}.show(binding.startDownloadDialogContainer)
.also {
@ -568,12 +575,18 @@ abstract class BaseBrowserFragment :
customThirdPartyDownloadDialog = { downloaderApps, onAppSelected, negativeActionCallback ->
run {
if (currentStartDownloadDialog == null) {
context.components.analytics.crashReporter.recordCrashBreadcrumb(
Breadcrumb("ThirdPartyDownloadDialog created"),
)
ThirdPartyDownloadDialog(
activity = requireActivity(),
downloaderApps = downloaderApps.value,
onAppSelected = onAppSelected.value,
negativeButtonAction = negativeActionCallback.value,
).onDismiss {
context.components.analytics.crashReporter.recordCrashBreadcrumb(
Breadcrumb("ThirdPartyDownloadDialog onDismiss"),
)
currentStartDownloadDialog = null
}.show(binding.startDownloadDialogContainer).also {
currentStartDownloadDialog = it

@ -30,6 +30,7 @@ import org.mozilla.fenix.components.metrics.GleanMetricsService
import org.mozilla.fenix.components.metrics.InstallReferrerMetricsService
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsStorage
import org.mozilla.fenix.crashes.CrashFactCollector
import org.mozilla.fenix.experiments.createNimbus
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
@ -120,6 +121,10 @@ class Analytics(
)
}
val crashFactCollector: CrashFactCollector by lazyMonitored {
CrashFactCollector(crashReporter)
}
val metricsStorage: MetricsStorage by lazyMonitored {
DefaultMetricsStorage(
context = context,

@ -0,0 +1,57 @@
/* 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.crashes
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.concept.base.crash.CrashReporting
import mozilla.components.feature.contextmenu.facts.ContextMenuFacts
import mozilla.components.feature.downloads.facts.DownloadsFacts
import mozilla.components.feature.prompts.facts.AddressAutofillDialogFacts
import mozilla.components.feature.prompts.facts.CreditCardAutofillDialogFacts
import mozilla.components.feature.prompts.facts.PromptFacts
import mozilla.components.feature.sitepermissions.SitePermissionsFacts
import mozilla.components.support.base.Component
import mozilla.components.support.base.facts.Fact
import mozilla.components.support.base.facts.FactProcessor
import mozilla.components.support.base.facts.Facts
/**
* Collects facts and record bread crumbs for the events.
*/
class CrashFactCollector(
private val crashReporter: CrashReporting,
) {
/**
* Starts collecting facts.
*/
fun start() {
Facts.registerProcessor(
object : FactProcessor {
override fun process(fact: Fact) {
fact.process()
}
},
)
}
internal fun Fact.process(): Unit = when (component to item) {
Component.FEATURE_CONTEXTMENU to ContextMenuFacts.Items.MENU,
Component.FEATURE_DOWNLOADS to CreditCardAutofillDialogFacts.Items.AUTOFILL_CREDIT_CARD_PROMPT_SHOWN,
Component.FEATURE_DOWNLOADS to CreditCardAutofillDialogFacts.Items.AUTOFILL_CREDIT_CARD_SAVE_PROMPT_SHOWN,
Component.FEATURE_DOWNLOADS to CreditCardAutofillDialogFacts.Items.AUTOFILL_CREDIT_CARD_PROMPT_DISMISSED,
Component.FEATURE_DOWNLOADS to AddressAutofillDialogFacts.Items.AUTOFILL_ADDRESS_PROMPT_SHOWN,
Component.FEATURE_DOWNLOADS to AddressAutofillDialogFacts.Items.AUTOFILL_ADDRESS_PROMPT_DISMISSED,
Component.FEATURE_DOWNLOADS to DownloadsFacts.Items.PROMPT,
Component.FEATURE_SITEPERMISSIONS to SitePermissionsFacts.Items.PERMISSIONS,
Component.FEATURE_PROMPTS to PromptFacts.Items.PROMPT,
-> {
crashReporter.recordCrashBreadcrumb(Breadcrumb("$component $action $value"))
}
else -> {
// no-op
}
}
}

@ -20,6 +20,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.children
import androidx.viewbinding.ViewBinding
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.feature.downloads.databinding.MozacDownloaderChooserPromptBinding
import mozilla.components.feature.downloads.toMegabyteOrKilobyteString
import mozilla.components.feature.downloads.ui.DownloaderApp
@ -27,6 +28,7 @@ import mozilla.components.feature.downloads.ui.DownloaderAppAdapter
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.DialogScrimBinding
import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
/**
@ -54,6 +56,9 @@ abstract class StartDownloadDialog(
* @param container The [ViewGroup] in which the download view will be inflated.
*/
fun show(container: ViewGroup): StartDownloadDialog {
activity.components.analytics.crashReporter.recordCrashBreadcrumb(
Breadcrumb("StartDownloadDialog show"),
)
this.container = container
val dialogParent = container.parent as? ViewGroup
@ -89,6 +94,9 @@ abstract class StartDownloadDialog(
* @param callback The callback for when the view is dismissed.
*/
fun onDismiss(callback: () -> Unit): StartDownloadDialog {
activity.components.analytics.crashReporter.recordCrashBreadcrumb(
Breadcrumb("StartDownloadDialog onDismiss"),
)
this.onDismiss = callback
return this
}

@ -19,6 +19,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.feature.accounts.push.SendTabUseCases
import mozilla.components.feature.share.RecentAppsStorage
@ -26,6 +27,7 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.databinding.FragmentShareBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.theme.FirefoxTheme
@ -49,11 +51,17 @@ class ShareFragment : AppCompatDialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("ShareFragment onCreate"),
)
setStyle(STYLE_NO_TITLE, R.style.ShareDialogStyle)
}
override fun onPause() {
super.onPause()
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("ShareFragment dismiss"),
)
consumePrompt { onDismiss() }
dismiss()
}
@ -150,6 +158,9 @@ class ShareFragment : AppCompatDialogFragment() {
}
override fun onDestroy() {
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("ShareFragment onDestroy"),
)
setFragmentResult(RESULT_KEY, Bundle())
// Clear the stored result in case there is no listener with the same key set.
clearFragmentResult(RESULT_KEY)

@ -29,6 +29,7 @@ import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.feature.downloads.ui.DownloadCancelDialogFragment
import mozilla.components.feature.tabs.tabstray.TabsFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
@ -125,6 +126,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment dismissTabsTray"),
)
setStyle(STYLE_NO_TITLE, R.style.TabTrayDialogStyle)
}
@ -190,13 +194,18 @@ class TabsTrayFragment : AppCompatDialogFragment() {
controller = tabsTrayController,
)
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment onCreateDialog"),
)
tabsTrayDialog = TabsTrayDialog(requireContext(), theme) { tabsTrayInteractor }
return tabsTrayDialog
}
override fun onPause() {
super.onPause()
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment onPause"),
)
dialog?.window?.setWindowAnimations(R.style.DialogFragmentRestoreAnimation)
}
@ -314,6 +323,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
override fun onStart() {
super.onStart()
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment onStart"),
)
findPreviousDialogFragment()?.let { dialog ->
dialog.onAcceptClicked = ::onCancelDownloadWarningAccepted
}
@ -321,6 +333,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
override fun onDestroyView() {
super.onDestroyView()
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment onDestroyView"),
)
_tabsTrayBinding = null
_tabsTrayDialogBinding = null
_fabButtonBinding = null
@ -551,6 +566,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
@VisibleForTesting
internal fun showCancelledDownloadWarning(downloadCount: Int, tabId: String?, source: String?) {
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("DownloadCancelDialogFragment show"),
)
val dialog = DownloadCancelDialogFragment.newInstance(
downloadCount = downloadCount,
tabId = tabId,
@ -687,6 +705,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
internal fun dismissTabsTray() {
// This should always be the last thing we do because nothing (e.g. telemetry)
// is guaranteed after that.
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment dismissTabsTray"),
)
dismissAllowingStateLoss()
}
@ -750,6 +771,9 @@ class TabsTrayFragment : AppCompatDialogFragment() {
}
private fun onTabsTrayDismissed() {
context?.components?.analytics?.crashReporter?.recordCrashBreadcrumb(
Breadcrumb("TabsTrayFragment onTabsTrayDismissed"),
)
TabsTray.closed.record(NoExtras())
dismissAllowingStateLoss()
}

@ -25,6 +25,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.StartDownloadDialogLayoutBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@ -44,6 +45,7 @@ class StartDownloadDialogTest {
mockkStatic("mozilla.components.support.ktx.android.view.WindowKt", "org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true)
every { any<Context>().components } returns mockk(relaxed = true)
val fluentDialog = dialog.show(dialogContainer)
val scrim = dialogParent.children.first { it.id == R.id.scrim }
@ -66,13 +68,16 @@ class StartDownloadDialogTest {
@Test
fun `GIVEN a dismiss callback WHEN the dialog is dismissed THEN the callback is informed`() {
var wasDismissCalled = false
val dialog = TestDownloadDialog(mockk(relaxed = true))
val fluentDialog = dialog.onDismiss { wasDismissCalled = true }
dialog.onDismiss()
val activity = Robolectric.buildActivity(Activity::class.java).create().get()
val dialog = TestDownloadDialog(activity)
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().components } returns mockk(relaxed = true)
val fluentDialog = dialog.onDismiss { wasDismissCalled = true }
dialog.onDismiss()
assertTrue(wasDismissCalled)
assertEquals(dialog, fluentDialog)
assertTrue(wasDismissCalled)
assertEquals(dialog, fluentDialog)
}
}
@Test
@ -86,6 +91,7 @@ class StartDownloadDialogTest {
val dialog = TestDownloadDialog(activity)
mockkStatic("mozilla.components.support.ktx.android.view.WindowKt", "org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true)
every { any<Context>().components } returns mockk(relaxed = true)
dialog.show(dialogContainer)
dialog.binding = StartDownloadDialogLayoutBinding
.inflate(LayoutInflater.from(activity), dialogContainer, true)
@ -161,6 +167,7 @@ class StartDownloadDialogTest {
every { accessibilityServicesEnabled } returns false
}
every { any<Context>().settings() } returns settings
every { any<Context>().components } returns mockk(relaxed = true)
dialog.show(dialogContainer)
assertEquals(2, dialogParent.children.count { it.isImportantForAccessibility })
@ -189,6 +196,7 @@ class StartDownloadDialogTest {
every { accessibilityServicesEnabled } returns true
}
every { any<Context>().settings() } returns settings
every { any<Context>().components } returns mockk(relaxed = true)
val dialog = TestDownloadDialog(activity)
dialog.show(dialogContainer)
dialog.binding = StartDownloadDialogLayoutBinding

@ -33,5 +33,5 @@ class RecentBookmarksViewHolderTest {
verify { interactor.onRemoveGroups(setOf(group)) }
}
*/
*/
}

@ -363,10 +363,12 @@ class TabsTrayFragmentTest {
@Test
fun `WHEN dismissTabsTray is called THEN it dismisses the tray`() {
every { fragment.dismissAllowingStateLoss() } just Runs
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().components } returns mockk(relaxed = true)
fragment.dismissTabsTray()
fragment.dismissTabsTray()
verify { fragment.dismissAllowingStateLoss() }
verify { fragment.dismissAllowingStateLoss() }
}
}
@Test

Loading…
Cancel
Save