Skip to content

Commit

Permalink
For mozilla-mobile#6687: Add crash reporting information for ClassCas…
Browse files Browse the repository at this point in the history
…tException in PromptFeature
  • Loading branch information
rocketsroger committed May 22, 2020
1 parent 8030dda commit 01b9aac
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ import mozilla.components.feature.prompts.file.FilePicker
import mozilla.components.feature.prompts.share.DefaultShareDelegate
import mozilla.components.feature.prompts.share.ShareDelegate
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.crash.Breadcrumb
import mozilla.components.support.base.crash.CrashReporting
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import java.lang.ClassCastException
import java.lang.ref.WeakReference
import java.security.InvalidParameterException
import java.util.Date
Expand Down Expand Up @@ -110,6 +113,7 @@ class PromptFeature private constructor(
private val fragmentManager: FragmentManager,
private val shareDelegate: ShareDelegate,
override val loginValidationDelegate: LoginValidationDelegate? = null,
private val crashReporting: CrashReporting? = null,
private val isSaveLoginEnabled: () -> Boolean = { false },
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : LifecycleAwareFeature, PermissionsFeature, Prompter {
Expand All @@ -131,6 +135,7 @@ class PromptFeature private constructor(
fragmentManager: FragmentManager,
shareDelegate: ShareDelegate = DefaultShareDelegate(),
loginValidationDelegate: LoginValidationDelegate? = null,
crashReporting: CrashReporting? = null,
isSaveLoginEnabled: () -> Boolean = { false },
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
Expand All @@ -140,6 +145,7 @@ class PromptFeature private constructor(
fragmentManager = fragmentManager,
shareDelegate = shareDelegate,
loginValidationDelegate = loginValidationDelegate,
crashReporting = crashReporting,
isSaveLoginEnabled = isSaveLoginEnabled,
onNeedToRequestPermissions = onNeedToRequestPermissions
)
Expand All @@ -151,6 +157,7 @@ class PromptFeature private constructor(
fragmentManager: FragmentManager,
shareDelegate: ShareDelegate = DefaultShareDelegate(),
loginValidationDelegate: LoginValidationDelegate? = null,
crashReporting: CrashReporting? = null,
isSaveLoginEnabled: () -> Boolean = { false },
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(
Expand All @@ -160,6 +167,7 @@ class PromptFeature private constructor(
fragmentManager = fragmentManager,
shareDelegate = shareDelegate,
loginValidationDelegate = loginValidationDelegate,
crashReporting = crashReporting,
isSaveLoginEnabled = isSaveLoginEnabled,
onNeedToRequestPermissions = onNeedToRequestPermissions
)
Expand All @@ -184,6 +192,7 @@ class PromptFeature private constructor(
fragmentManager = fragmentManager,
shareDelegate = DefaultShareDelegate(),
loginValidationDelegate = null,
crashReporting = null,
onNeedToRequestPermissions = onNeedToRequestPermissions
)

Expand Down Expand Up @@ -312,47 +321,58 @@ class PromptFeature private constructor(
@Suppress("UNCHECKED_CAST", "ComplexMethod")
override fun onConfirm(sessionId: String, value: Any?) {
store.consumePromptFrom(sessionId, activePrompt) {
when (it) {
is TimeSelection -> it.onConfirm(value as Date)
is Color -> it.onConfirm(value as String)
is Alert -> {
val shouldNotShowMoreDialogs = value as Boolean
promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs)
it.onConfirm(!shouldNotShowMoreDialogs)
}
is SingleChoice -> it.onConfirm(value as Choice)
is MenuChoice -> it.onConfirm(value as Choice)
is PromptRequest.Popup -> it.onAllow()
is MultipleChoice -> it.onConfirm(value as Array<Choice>)

is Authentication -> {
val (user, password) = value as Pair<String, String>
it.onConfirm(user, password)
}

is TextPrompt -> {
val (shouldNotShowMoreDialogs, text) = value as Pair<Boolean, String>

promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs)
it.onConfirm(!shouldNotShowMoreDialogs, text)
}
try {
when (it) {
is TimeSelection -> it.onConfirm(value as Date)
is Color -> it.onConfirm(value as String)
is Alert -> {
val shouldNotShowMoreDialogs = value as Boolean
promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs)
it.onConfirm(!shouldNotShowMoreDialogs)
}
is SingleChoice -> it.onConfirm(value as Choice)
is MenuChoice -> it.onConfirm(value as Choice)
is PromptRequest.Popup -> it.onAllow()
is MultipleChoice -> it.onConfirm(value as Array<Choice>)

is Authentication -> {
val (user, password) = value as Pair<String, String>
it.onConfirm(user, password)
}

is Share -> it.onSuccess()
is TextPrompt -> {
val (shouldNotShowMoreDialogs, text) = value as Pair<Boolean, String>

is LoginPrompt -> it.onConfirm(value as Login)
promptAbuserDetector.userWantsMoreDialogs(!shouldNotShowMoreDialogs)
it.onConfirm(!shouldNotShowMoreDialogs, text)
}

is PromptRequest.Confirm -> {
val (isCheckBoxChecked, buttonType) = value as Pair<Boolean, MultiButtonDialogFragment.ButtonType>
promptAbuserDetector.userWantsMoreDialogs(!isCheckBoxChecked)
when (buttonType) {
MultiButtonDialogFragment.ButtonType.POSITIVE ->
it.onConfirmPositiveButton(!isCheckBoxChecked)
MultiButtonDialogFragment.ButtonType.NEGATIVE ->
it.onConfirmNegativeButton(!isCheckBoxChecked)
MultiButtonDialogFragment.ButtonType.NEUTRAL ->
it.onConfirmNeutralButton(!isCheckBoxChecked)
is Share -> it.onSuccess()

is LoginPrompt -> it.onConfirm(value as Login)

is PromptRequest.Confirm -> {
val (isCheckBoxChecked, buttonType) =
value as Pair<Boolean, MultiButtonDialogFragment.ButtonType>
promptAbuserDetector.userWantsMoreDialogs(!isCheckBoxChecked)
when (buttonType) {
MultiButtonDialogFragment.ButtonType.POSITIVE ->
it.onConfirmPositiveButton(!isCheckBoxChecked)
MultiButtonDialogFragment.ButtonType.NEGATIVE ->
it.onConfirmNegativeButton(!isCheckBoxChecked)
MultiButtonDialogFragment.ButtonType.NEUTRAL ->
it.onConfirmNeutralButton(!isCheckBoxChecked)
}
}
}
} catch (e: ClassCastException) {
crashReporting?.recordCrashBreadcrumb(
Breadcrumb("PromptFeature onConsume cast failed",
hashMapOf("class name" to " ${it.javaClass}"), "crash",
Breadcrumb.Level.DEBUG, Breadcrumb.Type.NAVIGATION)
)

throw e
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import mozilla.components.feature.prompts.dialog.MultiButtonDialogFragment
import mozilla.components.feature.prompts.dialog.PromptDialogFragment
import mozilla.components.feature.prompts.file.FilePicker.Companion.FILE_PICKER_ACTIVITY_REQUEST_CODE
import mozilla.components.feature.prompts.share.ShareDelegate
import mozilla.components.support.base.crash.CrashReporting
import mozilla.components.support.test.any
import mozilla.components.support.test.eq
import mozilla.components.support.test.ext.joinBlocking
Expand Down Expand Up @@ -123,6 +124,7 @@ class PromptFeatureTest {
fragment = mock(),
store = store,
customTabId = "custom-tab",
crashReporting = mock(),
fragmentManager = fragmentManager) { }
)
feature.start()
Expand Down Expand Up @@ -961,6 +963,32 @@ class PromptFeatureTest {
assertFalse(prompt!!.shouldDismissOnLoad())
}

@Test
fun `PromptFeature adds breadcrumb when ClassCastException is triggered`() {
val crashReporting: CrashReporting = mock()
val feature = PromptFeature(
activity = mock(),
store = store,
fragmentManager = fragmentManager,
crashReporting = crashReporting
) { }
feature.start()

val singleChoiceRequest = SingleChoice(arrayOf()) {}
var classCastExceptionThrown = false
store.dispatch(ContentAction.UpdatePromptRequestAction(tabId, singleChoiceRequest)).joinBlocking()

try {
feature.onConfirm(tabId, "wrong")
} catch (e: ClassCastException) {
classCastExceptionThrown = true
}

store.waitUntilIdle()
assert(classCastExceptionThrown)
verify(crashReporting).recordCrashBreadcrumb(any())
}

private fun mockFragmentManager(): FragmentManager {
val fragmentManager: FragmentManager = mock()
val transaction: FragmentTransaction = mock()
Expand Down

0 comments on commit 01b9aac

Please sign in to comment.