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 {
 |