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