ma1 pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits:
-
cd556e14
by hackademix at 2024-08-02T22:31:10+02:00
-
c60897e8
by hackademix at 2024-08-02T22:57:38+02:00
-
1e885468
by hackademix at 2024-08-02T23:25:03+02:00
-
233d8217
by hackademix at 2024-08-04T17:32:36+02:00
7 changed files:
- android-components/.buildconfig.yml
- android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
- android-components/components/feature/sitepermissions/build.gradle
- android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt
- android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragmentTest.kt
- android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/PromptAbuserDetector.kt → android-components/components/support/utils/src/main/java/mozilla/components/support/ktx/util/PromptAbuserDetector.kt
- focus-android/app/src/androidTest/java/org/mozilla/focus/activity/SitePermissionsTest.kt
Changes:
| ... | ... | @@ -1150,7 +1150,6 @@ projects: |
| 1150 | 1150 | - concept-tabstray
|
| 1151 | 1151 | - concept-toolbar
|
| 1152 | 1152 | - feature-session
|
| 1153 | - - feature-prompts
|
|
| 1154 | 1153 | - feature-tabs
|
| 1155 | 1154 | - lib-publicsuffixlist
|
| 1156 | 1155 | - lib-state
|
| ... | ... | @@ -61,7 +61,6 @@ import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment.Companion. |
| 61 | 61 | import mozilla.components.feature.prompts.dialog.ColorPickerDialogFragment
|
| 62 | 62 | import mozilla.components.feature.prompts.dialog.ConfirmDialogFragment
|
| 63 | 63 | import mozilla.components.feature.prompts.dialog.MultiButtonDialogFragment
|
| 64 | -import mozilla.components.feature.prompts.dialog.PromptAbuserDetector
|
|
| 65 | 64 | import mozilla.components.feature.prompts.dialog.PromptDialogFragment
|
| 66 | 65 | import mozilla.components.feature.prompts.dialog.Prompter
|
| 67 | 66 | import mozilla.components.feature.prompts.dialog.SaveLoginDialogFragment
|
| ... | ... | @@ -91,6 +90,7 @@ import mozilla.components.support.base.feature.UserInteractionHandler |
| 91 | 90 | import mozilla.components.support.base.log.logger.Logger
|
| 92 | 91 | import mozilla.components.support.ktx.kotlin.ifNullOrEmpty
|
| 93 | 92 | import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
|
| 93 | +import mozilla.components.support.ktx.util.PromptAbuserDetector
|
|
| 94 | 94 | import java.lang.ref.WeakReference
|
| 95 | 95 | import java.security.InvalidParameterException
|
| 96 | 96 | import java.util.Collections
|
| ... | ... | @@ -467,36 +467,51 @@ class PromptFeature private constructor( |
| 467 | 467 | internal fun onPromptRequested(session: SessionState) {
|
| 468 | 468 | // Some requests are handle with intents
|
| 469 | 469 | session.content.promptRequests.lastOrNull()?.let { promptRequest ->
|
| 470 | - store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.let {
|
|
| 471 | - promptRequest.executeIfWindowedPrompt { exitFullscreenUsecase(it.id) }
|
|
| 470 | + if (session.content.permissionRequestsList.isNotEmpty()) {
|
|
| 471 | + onCancel(session.id, promptRequest.uid)
|
|
| 472 | + } else {
|
|
| 473 | + processPromptRequest(promptRequest, session)
|
|
| 472 | 474 | }
|
| 475 | + }
|
|
| 476 | + }
|
|
| 473 | 477 | |
| 474 | - when (promptRequest) {
|
|
| 475 | - is File -> {
|
|
| 476 | - emitPromptDisplayedFact(promptName = "FilePrompt")
|
|
| 477 | - filePicker.handleFileRequest(promptRequest)
|
|
| 478 | - }
|
|
| 479 | - is Share -> handleShareRequest(promptRequest, session)
|
|
| 480 | - is SelectCreditCard -> {
|
|
| 481 | - emitSuccessfulCreditCardAutofillFormDetectedFact()
|
|
| 482 | - if (isCreditCardAutofillEnabled() && promptRequest.creditCards.isNotEmpty()) {
|
|
| 483 | - creditCardPicker?.handleSelectCreditCardRequest(promptRequest)
|
|
| 484 | - }
|
|
| 478 | + @Suppress("NestedBlockDepth")
|
|
| 479 | + private fun processPromptRequest(
|
|
| 480 | + promptRequest: PromptRequest,
|
|
| 481 | + session: SessionState,
|
|
| 482 | + ) {
|
|
| 483 | + store.state.findTabOrCustomTabOrSelectedTab(customTabId)?.let {
|
|
| 484 | + promptRequest.executeIfWindowedPrompt { exitFullscreenUsecase(it.id) }
|
|
| 485 | + }
|
|
| 486 | + |
|
| 487 | + when (promptRequest) {
|
|
| 488 | + is File -> {
|
|
| 489 | + emitPromptDisplayedFact(promptName = "FilePrompt")
|
|
| 490 | + filePicker.handleFileRequest(promptRequest)
|
|
| 491 | + }
|
|
| 492 | + |
|
| 493 | + is Share -> handleShareRequest(promptRequest, session)
|
|
| 494 | + is SelectCreditCard -> {
|
|
| 495 | + emitSuccessfulCreditCardAutofillFormDetectedFact()
|
|
| 496 | + if (isCreditCardAutofillEnabled() && promptRequest.creditCards.isNotEmpty()) {
|
|
| 497 | + creditCardPicker?.handleSelectCreditCardRequest(promptRequest)
|
|
| 485 | 498 | }
|
| 486 | - is SelectLoginPrompt -> {
|
|
| 487 | - emitPromptDisplayedFact(promptName = "SelectLoginPrompt")
|
|
| 488 | - if (promptRequest.logins.isNotEmpty()) {
|
|
| 489 | - loginPicker?.handleSelectLoginRequest(promptRequest)
|
|
| 490 | - }
|
|
| 499 | + }
|
|
| 500 | + |
|
| 501 | + is SelectLoginPrompt -> {
|
|
| 502 | + emitPromptDisplayedFact(promptName = "SelectLoginPrompt")
|
|
| 503 | + if (promptRequest.logins.isNotEmpty()) {
|
|
| 504 | + loginPicker?.handleSelectLoginRequest(promptRequest)
|
|
| 491 | 505 | }
|
| 492 | - is SelectAddress -> {
|
|
| 493 | - emitSuccessfulAddressAutofillFormDetectedFact()
|
|
| 494 | - if (isAddressAutofillEnabled() && promptRequest.addresses.isNotEmpty()) {
|
|
| 495 | - addressPicker?.handleSelectAddressRequest(promptRequest)
|
|
| 496 | - }
|
|
| 506 | + }
|
|
| 507 | + is SelectAddress -> {
|
|
| 508 | + emitSuccessfulAddressAutofillFormDetectedFact()
|
|
| 509 | + if (isAddressAutofillEnabled() && promptRequest.addresses.isNotEmpty()) {
|
|
| 510 | + addressPicker?.handleSelectAddressRequest(promptRequest)
|
|
| 497 | 511 | }
|
| 498 | - else -> handleDialogsRequest(promptRequest, session)
|
|
| 499 | 512 | }
|
| 513 | + |
|
| 514 | + else -> handleDialogsRequest(promptRequest, session)
|
|
| 500 | 515 | }
|
| 501 | 516 | }
|
| 502 | 517 |
| ... | ... | @@ -58,8 +58,8 @@ dependencies { |
| 58 | 58 | implementation project(':concept-engine')
|
| 59 | 59 | implementation project(':ui-icons')
|
| 60 | 60 | implementation project(':support-ktx')
|
| 61 | - implementation project(':feature-prompts')
|
|
| 62 | 61 | implementation project(':feature-tabs')
|
| 62 | + implementation project(':support-utils')
|
|
| 63 | 63 | |
| 64 | 64 | implementation ComponentsDependencies.kotlin_coroutines
|
| 65 | 65 |
| ... | ... | @@ -23,7 +23,7 @@ import android.widget.TextView |
| 23 | 23 | import androidx.annotation.VisibleForTesting
|
| 24 | 24 | import androidx.appcompat.app.AppCompatDialogFragment
|
| 25 | 25 | import androidx.core.content.ContextCompat
|
| 26 | -import mozilla.components.feature.prompts.dialog.PromptAbuserDetector
|
|
| 26 | +import mozilla.components.support.ktx.util.PromptAbuserDetector
|
|
| 27 | 27 | |
| 28 | 28 | internal const val KEY_SESSION_ID = "KEY_SESSION_ID"
|
| 29 | 29 | internal const val KEY_TITLE = "KEY_TITLE"
|
| ... | ... | @@ -19,6 +19,7 @@ import mozilla.components.support.test.robolectric.testContext |
| 19 | 19 | import org.junit.Assert.assertEquals
|
| 20 | 20 | import org.junit.Assert.assertFalse
|
| 21 | 21 | import org.junit.Assert.assertTrue
|
| 22 | +import org.junit.Ignore
|
|
| 22 | 23 | import org.junit.Test
|
| 23 | 24 | import org.junit.runner.RunWith
|
| 24 | 25 | import org.mockito.Mockito.doNothing
|
| ... | ... | @@ -233,6 +234,7 @@ class SitePermissionsDialogFragmentTest { |
| 233 | 234 | }
|
| 234 | 235 | |
| 235 | 236 | @Test
|
| 237 | + @Ignore("https://bugzilla.mozilla.org/show_bug.cgi?id=1903828")
|
|
| 236 | 238 | fun `clicking on positive button notifies the feature (temporary)`() {
|
| 237 | 239 | val mockFeature: SitePermissionsFeature = mock()
|
| 238 | 240 | val fragment = spy(
|
| ... | ... | @@ -261,6 +263,7 @@ class SitePermissionsDialogFragmentTest { |
| 261 | 263 | }
|
| 262 | 264 | |
| 263 | 265 | @Test
|
| 266 | + @Ignore("https://bugzilla.mozilla.org/show_bug.cgi?id=1903828")
|
|
| 264 | 267 | fun `dismissing the dialog notifies the feature`() {
|
| 265 | 268 | val mockFeature: SitePermissionsFeature = mock()
|
| 266 | 269 | val fragment = spy(
|
| ... | ... | @@ -360,6 +363,7 @@ class SitePermissionsDialogFragmentTest { |
| 360 | 363 | }
|
| 361 | 364 | |
| 362 | 365 | @Test
|
| 366 | + @Ignore("https://bugzilla.mozilla.org/show_bug.cgi?id=1903828")
|
|
| 363 | 367 | fun `clicking on positive button notifies the feature (permanent)`() {
|
| 364 | 368 | val mockFeature: SitePermissionsFeature = mock()
|
| 365 | 369 | val fragment = spy(
|
| ... | ... | @@ -389,6 +393,7 @@ class SitePermissionsDialogFragmentTest { |
| 389 | 393 | }
|
| 390 | 394 | |
| 391 | 395 | @Test
|
| 396 | + @Ignore("https://bugzilla.mozilla.org/show_bug.cgi?id=1903828")
|
|
| 392 | 397 | fun `clicking on negative button notifies the feature (permanent)`() {
|
| 393 | 398 | val mockFeature: SitePermissionsFeature = mock()
|
| 394 | 399 | val fragment = spy(
|
| ... | ... | @@ -2,25 +2,38 @@ |
| 2 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 3 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
| 4 | 4 | |
| 5 | -package mozilla.components.feature.prompts.dialog
|
|
| 5 | +package mozilla.components.support.ktx.util
|
|
| 6 | 6 | |
| 7 | +import androidx.annotation.VisibleForTesting
|
|
| 7 | 8 | import java.util.Date
|
| 8 | 9 | |
| 9 | 10 | /**
|
| 10 | 11 | * Helper class to identify if a website has shown many dialogs.
|
| 12 | + * @param maxSuccessiveDialogSecondsLimit Maximum time required
|
|
| 13 | + * between dialogs in seconds before not showing more dialog.
|
|
| 11 | 14 | */
|
| 12 | 15 | class PromptAbuserDetector(private val maxSuccessiveDialogSecondsLimit: Int = MAX_SUCCESSIVE_DIALOG_SECONDS_LIMIT) {
|
| 13 | 16 | |
| 14 | - internal var jsAlertCount = 0
|
|
| 15 | - internal var lastDialogShownAt = Date()
|
|
| 17 | + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
| 18 | + var jsAlertCount = 0
|
|
| 19 | + |
|
| 20 | + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
| 21 | + var lastDialogShownAt = Date()
|
|
| 22 | + |
|
| 16 | 23 | var shouldShowMoreDialogs = true
|
| 17 | 24 | private set
|
| 18 | 25 | |
| 26 | + /**
|
|
| 27 | + * Updates internal state for alerts counts.
|
|
| 28 | + */
|
|
| 19 | 29 | fun resetJSAlertAbuseState() {
|
| 20 | 30 | jsAlertCount = 0
|
| 21 | 31 | shouldShowMoreDialogs = true
|
| 22 | 32 | }
|
| 23 | 33 | |
| 34 | + /**
|
|
| 35 | + * Updates internal state for last shown and count of dialogs.
|
|
| 36 | + */
|
|
| 24 | 37 | fun updateJSDialogAbusedState() {
|
| 25 | 38 | if (!areDialogsAbusedByTime()) {
|
| 26 | 39 | jsAlertCount = 0
|
| ... | ... | @@ -29,25 +42,35 @@ class PromptAbuserDetector(private val maxSuccessiveDialogSecondsLimit: Int = MA |
| 29 | 42 | lastDialogShownAt = Date()
|
| 30 | 43 | }
|
| 31 | 44 | |
| 45 | + /**
|
|
| 46 | + * Indicates whether or not user wants to see more dialogs.
|
|
| 47 | + */
|
|
| 32 | 48 | fun userWantsMoreDialogs(checkBox: Boolean) {
|
| 33 | 49 | shouldShowMoreDialogs = checkBox
|
| 34 | 50 | }
|
| 35 | 51 | |
| 52 | + /**
|
|
| 53 | + * Indicates whether dialogs are being abused or not.
|
|
| 54 | + */
|
|
| 36 | 55 | fun areDialogsBeingAbused(): Boolean {
|
| 37 | 56 | return areDialogsAbusedByTime() || areDialogsAbusedByCount()
|
| 38 | 57 | }
|
| 39 | 58 | |
| 40 | - internal fun areDialogsAbusedByTime(): Boolean {
|
|
| 59 | + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
| 60 | + @Suppress("UndocumentedPublicFunction") // this is visible only for tests
|
|
| 61 | + fun now() = Date()
|
|
| 62 | + |
|
| 63 | + private fun areDialogsAbusedByTime(): Boolean {
|
|
| 41 | 64 | return if (jsAlertCount == 0) {
|
| 42 | 65 | false
|
| 43 | 66 | } else {
|
| 44 | - val now = Date()
|
|
| 67 | + val now = now()
|
|
| 45 | 68 | val diffInSeconds = (now.time - lastDialogShownAt.time) / SECOND_MS
|
| 46 | 69 | diffInSeconds < maxSuccessiveDialogSecondsLimit
|
| 47 | 70 | }
|
| 48 | 71 | }
|
| 49 | 72 | |
| 50 | - internal fun areDialogsAbusedByCount(): Boolean {
|
|
| 73 | + private fun areDialogsAbusedByCount(): Boolean {
|
|
| 51 | 74 | return jsAlertCount > MAX_SUCCESSIVE_DIALOG_COUNT
|
| 52 | 75 | }
|
| 53 | 76 |
| ... | ... | @@ -229,6 +229,7 @@ class SitePermissionsTest { |
| 229 | 229 | |
| 230 | 230 | @SmokeTest
|
| 231 | 231 | @Test
|
| 232 | + @Ignore
|
|
| 232 | 233 | fun testLocationSharingAllowed() {
|
| 233 | 234 | mockLocationUpdatesRule.setMockLocation()
|
| 234 | 235 | |
| ... | ... | @@ -244,6 +245,7 @@ class SitePermissionsTest { |
| 244 | 245 | |
| 245 | 246 | @SmokeTest
|
| 246 | 247 | @Test
|
| 248 | + @Ignore
|
|
| 247 | 249 | fun allowCameraPermissionsTest() {
|
| 248 | 250 | Assume.assumeTrue(cameraManager.cameraIdList.isNotEmpty())
|
| 249 | 251 | searchScreen {
|