ma1 pushed to branch firefox-android-115.2.1-13.0-1 at The Tor Project / Applications / firefox-android
Commits:
-
d3aa11b9
by hackademix at 2024-05-08T18:01:13+02:00
-
43756a25
by Arturo Mejia at 2024-05-08T19:23:07+02:00
-
44c271d8
by hackademix at 2024-05-08T19:29:05+02:00
-
f72ebb33
by hackademix at 2024-05-08T20:40:25+02:00
6 changed files:
- android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsFeature.kt
- android-components/components/feature/sitepermissions/src/test/java/mozilla/components/feature/sitepermissions/SitePermissionsFeatureTest.kt
- android-components/components/feature/webauthn/src/main/java/mozilla/components/feature/webauthn/WebAuthnFeature.kt
- android-components/components/feature/webauthn/src/test/java/mozilla/components/feature/webauthn/WebAuthnFeatureTest.kt
- fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
- fenix/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt
Changes:
| ... | ... | @@ -56,12 +56,14 @@ import mozilla.components.concept.engine.permission.SitePermissions |
| 56 | 56 | import mozilla.components.concept.engine.permission.SitePermissions.Status.ALLOWED
|
| 57 | 57 | import mozilla.components.concept.engine.permission.SitePermissions.Status.BLOCKED
|
| 58 | 58 | import mozilla.components.concept.engine.permission.SitePermissionsStorage
|
| 59 | +import mozilla.components.feature.session.SessionUseCases
|
|
| 59 | 60 | import mozilla.components.feature.sitepermissions.SitePermissionsFeature.DialogConfig
|
| 60 | 61 | import mozilla.components.feature.tabs.TabsUseCases.SelectOrAddUseCase
|
| 61 | 62 | import mozilla.components.lib.state.ext.flowScoped
|
| 62 | 63 | import mozilla.components.support.base.feature.LifecycleAwareFeature
|
| 63 | 64 | import mozilla.components.support.base.feature.OnNeedToRequestPermissions
|
| 64 | 65 | import mozilla.components.support.base.feature.PermissionsFeature
|
| 66 | +import mozilla.components.support.base.log.logger.Logger
|
|
| 65 | 67 | import mozilla.components.support.ktx.android.content.isPermissionGranted
|
| 66 | 68 | import mozilla.components.support.ktx.kotlin.getOrigin
|
| 67 | 69 | import mozilla.components.support.ktx.kotlin.stripDefaultPort
|
| ... | ... | @@ -72,8 +74,6 @@ import mozilla.components.ui.icons.R as iconsR |
| 72 | 74 | |
| 73 | 75 | internal const val PROMPT_FRAGMENT_TAG = "mozac_feature_sitepermissions_prompt_dialog"
|
| 74 | 76 | |
| 75 | -private const val FULL_SCREEN_NOTIFICATION_TAG = "mozac_feature_prompts_full_screen_notification_dialog"
|
|
| 76 | - |
|
| 77 | 77 | @VisibleForTesting
|
| 78 | 78 | internal const val STORAGE_ACCESS_DOCUMENTATION_URL =
|
| 79 | 79 | "https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API"
|
| ... | ... | @@ -94,13 +94,15 @@ internal const val STORAGE_ACCESS_DOCUMENTATION_URL = |
| 94 | 94 | * need to be requested. Once the request is completed, [onPermissionsResult] needs to be invoked.
|
| 95 | 95 | * @property onShouldShowRequestPermissionRationale a callback that allows the feature to query
|
| 96 | 96 | * the ActivityCompat.shouldShowRequestPermissionRationale or the Fragment.shouldShowRequestPermissionRationale values.
|
| 97 | + * @property exitFullscreenUseCase optional the use case in charge of exiting fullscreen
|
|
| 97 | 98 | * @property shouldShowDoNotAskAgainCheckBox optional Visibility for Do not ask again Checkbox
|
| 98 | 99 | **/
|
| 99 | 100 | |
| 100 | 101 | @Suppress("TooManyFunctions", "LargeClass", "LongParameterList")
|
| 101 | 102 | class SitePermissionsFeature(
|
| 102 | 103 | private val context: Context,
|
| 103 | - private var sessionId: String? = null,
|
|
| 104 | + @set:VisibleForTesting
|
|
| 105 | + internal var sessionId: String? = null,
|
|
| 104 | 106 | private val storage: SitePermissionsStorage = OnDiskSitePermissionsStorage(context),
|
| 105 | 107 | var sitePermissionsRules: SitePermissionsRules? = null,
|
| 106 | 108 | private val fragmentManager: FragmentManager,
|
| ... | ... | @@ -109,6 +111,7 @@ class SitePermissionsFeature( |
| 109 | 111 | override val onNeedToRequestPermissions: OnNeedToRequestPermissions,
|
| 110 | 112 | val onShouldShowRequestPermissionRationale: (permission: String) -> Boolean,
|
| 111 | 113 | private val store: BrowserStore,
|
| 114 | + private val exitFullscreenUseCase: SessionUseCases.ExitFullScreenUseCase = SessionUseCases(store).exitFullscreen,
|
|
| 112 | 115 | private val shouldShowDoNotAskAgainCheckBox: Boolean = true,
|
| 113 | 116 | ) : LifecycleAwareFeature, PermissionsFeature {
|
| 114 | 117 | @VisibleForTesting
|
| ... | ... | @@ -116,6 +119,8 @@ class SitePermissionsFeature( |
| 116 | 119 | SelectOrAddUseCase(store)
|
| 117 | 120 | }
|
| 118 | 121 | |
| 122 | + private val logger = Logger("SitePermissionsFeature")
|
|
| 123 | + |
|
| 119 | 124 | internal val ioCoroutineScope by lazy { coroutineScopeInitializer() }
|
| 120 | 125 | |
| 121 | 126 | internal var coroutineScopeInitializer = {
|
| ... | ... | @@ -428,26 +433,29 @@ class SitePermissionsFeature( |
| 428 | 433 | consumePermissionRequest(permissionRequest)
|
| 429 | 434 | return null
|
| 430 | 435 | }
|
| 431 | - |
|
| 432 | - val private: Boolean = store.state.findTabOrCustomTabOrSelectedTab(sessionId)?.content?.private
|
|
| 433 | - ?: throw IllegalStateException("Unable to find session for $sessionId or selected session")
|
|
| 436 | + val tab = store.state.findTabOrCustomTabOrSelectedTab(sessionId)
|
|
| 437 | + if (tab == null) {
|
|
| 438 | + logger.error("Unable to find a tab for $sessionId rejecting the prompt request")
|
|
| 439 | + permissionRequest.reject()
|
|
| 440 | + consumePermissionRequest(permissionRequest)
|
|
| 441 | + return null
|
|
| 442 | + }
|
|
| 434 | 443 | |
| 435 | 444 | val permissionFromStorage = withContext(coroutineScope.coroutineContext) {
|
| 436 | - storage.findSitePermissionsBy(origin, private = private)
|
|
| 445 | + storage.findSitePermissionsBy(origin, private = tab.content.private)
|
|
| 437 | 446 | }
|
| 438 | - |
|
| 439 | 447 | val prompt = if (shouldApplyRules(permissionFromStorage)) {
|
| 440 | 448 | handleRuledFlow(permissionRequest, origin)
|
| 441 | 449 | } else {
|
| 442 | 450 | handleNoRuledFlow(permissionFromStorage, permissionRequest, origin)
|
| 443 | 451 | }
|
| 444 | 452 | |
| 445 | - val fullScreenNotificationDisplayed =
|
|
| 446 | - fragmentManager.fragments.any { fragment -> fragment.tag == FULL_SCREEN_NOTIFICATION_TAG }
|
|
| 447 | - |
|
| 448 | - return if (fullScreenNotificationDisplayed || prompt == null) {
|
|
| 453 | + return if (prompt == null) {
|
|
| 449 | 454 | null
|
| 450 | 455 | } else {
|
| 456 | + // If we are in fullscreen, then exit to show the permission prompt.
|
|
| 457 | + // This won't have any effect if we are not in fullscreen.
|
|
| 458 | + exitFullscreenUseCase.invoke(tab.id)
|
|
| 451 | 459 | prompt.show(fragmentManager, PROMPT_FRAGMENT_TAG)
|
| 452 | 460 | prompt
|
| 453 | 461 | }
|
| ... | ... | @@ -600,6 +600,24 @@ class SitePermissionsFeatureTest { |
| 600 | 600 | verify(sitePermissionFeature).consumePermissionRequest(mockPermissionRequest)
|
| 601 | 601 | }
|
| 602 | 602 | |
| 603 | + @Test
|
|
| 604 | + fun `GIVEN sessionId which does not match a selected or custom tab WHEN onContentPermissionRequested() THEN reject, consumePermissionRequest are called `() {
|
|
| 605 | + val mockPermissionRequest: PermissionRequest = mock {
|
|
| 606 | + whenever(permissions).thenReturn(listOf(ContentVideoCamera(id = "permission")))
|
|
| 607 | + }
|
|
| 608 | + |
|
| 609 | + doNothing().`when`(mockPermissionRequest).reject()
|
|
| 610 | + |
|
| 611 | + sitePermissionFeature.sessionId = null
|
|
| 612 | + |
|
| 613 | + runTestOnMain {
|
|
| 614 | + sitePermissionFeature.onContentPermissionRequested(mockPermissionRequest, URL)
|
|
| 615 | + }
|
|
| 616 | + |
|
| 617 | + verify(mockPermissionRequest).reject()
|
|
| 618 | + verify(sitePermissionFeature).consumePermissionRequest(mockPermissionRequest)
|
|
| 619 | + }
|
|
| 620 | + |
|
| 603 | 621 | @Test
|
| 604 | 622 | fun `GIVEN location permissionRequest and shouldApplyRules is true WHEN onContentPermissionRequested() THEN handleRuledFlow is called`() = runTestOnMain {
|
| 605 | 623 | // given
|
| ... | ... | @@ -20,6 +20,8 @@ import mozilla.components.support.base.log.logger.Logger |
| 20 | 20 | class WebAuthnFeature(
|
| 21 | 21 | private val engine: Engine,
|
| 22 | 22 | private val activity: Activity,
|
| 23 | + private val exitFullScreen: (String?) -> Unit,
|
|
| 24 | + private val currentTab: () -> String?,
|
|
| 23 | 25 | ) : LifecycleAwareFeature, ActivityResultHandler, ActivityDelegate {
|
| 24 | 26 | private val logger = Logger("WebAuthnFeature")
|
| 25 | 27 | private var requestCodeCounter = ACTIVITY_REQUEST_CODE
|
| ... | ... | @@ -53,6 +55,7 @@ class WebAuthnFeature( |
| 53 | 55 | |
| 54 | 56 | override fun startIntentSenderForResult(intent: IntentSender, onResult: (Intent?) -> Unit) {
|
| 55 | 57 | logger.info("Received activity delegate request with code: $requestCodeCounter")
|
| 58 | + exitFullScreen(currentTab())
|
|
| 56 | 59 | activity.startIntentSenderForResult(intent, requestCodeCounter, null, 0, 0, 0)
|
| 57 | 60 | callbackRef = onResult
|
| 58 | 61 | }
|
| ... | ... | @@ -22,6 +22,8 @@ import org.mockito.Mockito.verify |
| 22 | 22 | class WebAuthnFeatureTest {
|
| 23 | 23 | private lateinit var engine: Engine
|
| 24 | 24 | private lateinit var activity: Activity
|
| 25 | + private val exitFullScreen: (String?) -> Unit = { _ -> exitFullScreenUseCaseCalled = true }
|
|
| 26 | + private var exitFullScreenUseCaseCalled = false
|
|
| 25 | 27 | |
| 26 | 28 | @Before
|
| 27 | 29 | fun setup() {
|
| ... | ... | @@ -31,7 +33,7 @@ class WebAuthnFeatureTest { |
| 31 | 33 | |
| 32 | 34 | @Test
|
| 33 | 35 | fun `feature registers itself on start`() {
|
| 34 | - val feature = WebAuthnFeature(engine, activity)
|
|
| 36 | + val feature = webAuthnFeature()
|
|
| 35 | 37 | |
| 36 | 38 | feature.start()
|
| 37 | 39 | |
| ... | ... | @@ -40,7 +42,7 @@ class WebAuthnFeatureTest { |
| 40 | 42 | |
| 41 | 43 | @Test
|
| 42 | 44 | fun `feature unregisters itself on stop`() {
|
| 43 | - val feature = WebAuthnFeature(engine, activity)
|
|
| 45 | + val feature = webAuthnFeature()
|
|
| 44 | 46 | |
| 45 | 47 | feature.stop()
|
| 46 | 48 | |
| ... | ... | @@ -49,7 +51,7 @@ class WebAuthnFeatureTest { |
| 49 | 51 | |
| 50 | 52 | @Test
|
| 51 | 53 | fun `activity delegate starts intent sender`() {
|
| 52 | - val feature = WebAuthnFeature(engine, activity)
|
|
| 54 | + val feature = webAuthnFeature()
|
|
| 53 | 55 | val callback: ((Intent?) -> Unit) = { }
|
| 54 | 56 | val intentSender: IntentSender = mock()
|
| 55 | 57 | |
| ... | ... | @@ -60,7 +62,7 @@ class WebAuthnFeatureTest { |
| 60 | 62 | |
| 61 | 63 | @Test
|
| 62 | 64 | fun `callback is invoked`() {
|
| 63 | - val feature = WebAuthnFeature(engine, activity)
|
|
| 65 | + val feature = webAuthnFeature()
|
|
| 64 | 66 | var callbackInvoked = false
|
| 65 | 67 | val callback: ((Intent?) -> Unit) = { callbackInvoked = true }
|
| 66 | 68 | val intentSender: IntentSender = mock()
|
| ... | ... | @@ -77,10 +79,14 @@ class WebAuthnFeatureTest { |
| 77 | 79 | |
| 78 | 80 | @Test
|
| 79 | 81 | fun `feature won't process results with the wrong request code`() {
|
| 80 | - val feature = WebAuthnFeature(engine, activity)
|
|
| 82 | + val feature = webAuthnFeature()
|
|
| 81 | 83 | |
| 82 | 84 | val result = feature.onActivityResult(ACTIVITY_REQUEST_CODE - 5, Intent(), 0)
|
| 83 | 85 | |
| 84 | 86 | assertFalse(result)
|
| 85 | 87 | }
|
| 88 | + |
|
| 89 | + private fun webAuthnFeature(): WebAuthnFeature {
|
|
| 90 | + return WebAuthnFeature(engine, activity, { exitFullScreen("") }) { "" }
|
|
| 91 | + }
|
|
| 86 | 92 | } |
| ... | ... | @@ -830,6 +830,8 @@ abstract class BaseBrowserFragment : |
| 830 | 830 | feature = WebAuthnFeature(
|
| 831 | 831 | engine = requireComponents.core.engine,
|
| 832 | 832 | activity = requireActivity(),
|
| 833 | + exitFullScreen = requireComponents.useCases.sessionUseCases.exitFullscreen::invoke,
|
|
| 834 | + currentTab = { store.state.selectedTabId },
|
|
| 833 | 835 | ),
|
| 834 | 836 | owner = this,
|
| 835 | 837 | view = view,
|
| ... | ... | @@ -71,6 +71,7 @@ class ShareFragment : AppCompatDialogFragment() { |
| 71 | 71 | container: ViewGroup?,
|
| 72 | 72 | savedInstanceState: Bundle?,
|
| 73 | 73 | ): View {
|
| 74 | + requireComponents.useCases.sessionUseCases.exitFullscreen.invoke()
|
|
| 74 | 75 | val binding = FragmentShareBinding.inflate(
|
| 75 | 76 | inflater,
|
| 76 | 77 | container,
|