mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 20:01:50 +00:00
Bug 1914244 - Move the allow in private browsing checkbox to the web extension permissions prompt. r=willdurand,zmckenney,geckoview-reviewers,owlish,android-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D219839
This commit is contained in:
parent
ede8ff25ca
commit
45921cf16a
@ -81,6 +81,8 @@ import org.mozilla.geckoview.WebExtensionController
|
||||
import org.mozilla.geckoview.WebNotification
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
typealias NativePermissionPromptResponse = org.mozilla.geckoview.WebExtension.PermissionPromptResponse
|
||||
|
||||
/**
|
||||
* Gecko-based implementation of Engine interface.
|
||||
*/
|
||||
@ -342,20 +344,26 @@ class GeckoEngine(
|
||||
this.webExtensionDelegate = webExtensionDelegate
|
||||
|
||||
val promptDelegate = object : WebExtensionController.PromptDelegate {
|
||||
override fun onInstallPrompt(
|
||||
|
||||
override fun onInstallPromptRequest(
|
||||
ext: org.mozilla.geckoview.WebExtension,
|
||||
permissions: Array<out String>,
|
||||
origins: Array<out String>,
|
||||
): GeckoResult<AllowOrDeny>? {
|
||||
val result = GeckoResult<AllowOrDeny>()
|
||||
): GeckoResult<NativePermissionPromptResponse>? {
|
||||
val result = GeckoResult<NativePermissionPromptResponse>()
|
||||
|
||||
webExtensionDelegate.onInstallPermissionRequest(
|
||||
GeckoWebExtension(ext, runtime),
|
||||
// We pass both permissions and origins as a single list of
|
||||
// permissions to be shown to the user.
|
||||
permissions.toList() + origins.toList(),
|
||||
) { allow ->
|
||||
if (allow) result.complete(AllowOrDeny.ALLOW) else result.complete(AllowOrDeny.DENY)
|
||||
) { data ->
|
||||
result.complete(
|
||||
NativePermissionPromptResponse(
|
||||
data.isPermissionsGranted,
|
||||
data.isPrivateModeGranted,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -36,6 +36,7 @@ import mozilla.components.concept.engine.translate.OperationLevel
|
||||
import mozilla.components.concept.engine.utils.EngineReleaseChannel
|
||||
import mozilla.components.concept.engine.webextension.Action
|
||||
import mozilla.components.concept.engine.webextension.InstallationMethod
|
||||
import mozilla.components.concept.engine.webextension.PermissionPromptResponse
|
||||
import mozilla.components.concept.engine.webextension.WebExtension
|
||||
import mozilla.components.concept.engine.webextension.WebExtensionDelegate
|
||||
import mozilla.components.concept.engine.webextension.WebExtensionException
|
||||
@ -1384,10 +1385,11 @@ class GeckoEngineTest {
|
||||
val geckoDelegateCaptor = argumentCaptor<WebExtensionController.PromptDelegate>()
|
||||
verify(webExtensionController).promptDelegate = geckoDelegateCaptor.capture()
|
||||
|
||||
val result = geckoDelegateCaptor.value.onInstallPrompt(extension, permissions, origins)
|
||||
val result =
|
||||
geckoDelegateCaptor.value.onInstallPromptRequest(extension, permissions, origins)
|
||||
|
||||
val extensionCaptor = argumentCaptor<WebExtension>()
|
||||
val onConfirmCaptor = argumentCaptor<((Boolean) -> Unit)>()
|
||||
val onConfirmCaptor = argumentCaptor<((PermissionPromptResponse) -> Unit)>()
|
||||
|
||||
verify(webExtensionsDelegate).onInstallPermissionRequest(
|
||||
extensionCaptor.capture(),
|
||||
@ -1395,9 +1397,66 @@ class GeckoEngineTest {
|
||||
onConfirmCaptor.capture(),
|
||||
)
|
||||
|
||||
onConfirmCaptor.value(true)
|
||||
onConfirmCaptor.value(
|
||||
PermissionPromptResponse(
|
||||
isPermissionsGranted = true,
|
||||
isPrivateModeGranted = false,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(GeckoResult.allow(), result)
|
||||
var nativePermissionPromptResponse: NativePermissionPromptResponse? = null
|
||||
result!!.accept {
|
||||
nativePermissionPromptResponse = it
|
||||
}
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
assertTrue(nativePermissionPromptResponse!!.isPermissionsGranted!!)
|
||||
assertFalse(nativePermissionPromptResponse!!.isPrivateModeGranted!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN permissions granted AND private mode granted WHEN onInstallPermissionRequest THEN delegate is called with all modes allowed`() {
|
||||
val runtime: GeckoRuntime = mock()
|
||||
val webExtensionController: WebExtensionController = mock()
|
||||
whenever(runtime.webExtensionController).thenReturn(webExtensionController)
|
||||
|
||||
val extension = mockNativeWebExtension("test", "uri")
|
||||
val permissions = arrayOf("some", "permissions")
|
||||
val origins = arrayOf("and some", "origins")
|
||||
val webExtensionsDelegate: WebExtensionDelegate = mock()
|
||||
val engine = GeckoEngine(context, runtime = runtime)
|
||||
|
||||
engine.registerWebExtensionDelegate(webExtensionsDelegate)
|
||||
|
||||
val geckoDelegateCaptor = argumentCaptor<WebExtensionController.PromptDelegate>()
|
||||
verify(webExtensionController).promptDelegate = geckoDelegateCaptor.capture()
|
||||
|
||||
val result = geckoDelegateCaptor.value.onInstallPromptRequest(extension, permissions, origins)
|
||||
|
||||
val extensionCaptor = argumentCaptor<WebExtension>()
|
||||
val onConfirmCaptor = argumentCaptor<((PermissionPromptResponse) -> Unit)>()
|
||||
|
||||
verify(webExtensionsDelegate).onInstallPermissionRequest(
|
||||
extensionCaptor.capture(),
|
||||
eq(permissions.asList() + origins.asList()),
|
||||
onConfirmCaptor.capture(),
|
||||
)
|
||||
|
||||
onConfirmCaptor.value(
|
||||
PermissionPromptResponse(
|
||||
isPermissionsGranted = true,
|
||||
isPrivateModeGranted = true,
|
||||
),
|
||||
)
|
||||
|
||||
var nativePermissionPromptResponse: NativePermissionPromptResponse? = null
|
||||
result!!.accept {
|
||||
nativePermissionPromptResponse = it
|
||||
}
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
assertTrue(nativePermissionPromptResponse!!.isPermissionsGranted!!)
|
||||
assertTrue(nativePermissionPromptResponse!!.isPrivateModeGranted!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1417,10 +1476,11 @@ class GeckoEngineTest {
|
||||
val geckoDelegateCaptor = argumentCaptor<WebExtensionController.PromptDelegate>()
|
||||
verify(webExtensionController).promptDelegate = geckoDelegateCaptor.capture()
|
||||
|
||||
val result = geckoDelegateCaptor.value.onInstallPrompt(extension, permissions, origins)
|
||||
val result =
|
||||
geckoDelegateCaptor.value.onInstallPromptRequest(extension, permissions, origins)
|
||||
|
||||
val extensionCaptor = argumentCaptor<WebExtension>()
|
||||
val onConfirmCaptor = argumentCaptor<((Boolean) -> Unit)>()
|
||||
val onConfirmCaptor = argumentCaptor<((PermissionPromptResponse) -> Unit)>()
|
||||
|
||||
verify(webExtensionsDelegate).onInstallPermissionRequest(
|
||||
extensionCaptor.capture(),
|
||||
@ -1428,9 +1488,21 @@ class GeckoEngineTest {
|
||||
onConfirmCaptor.capture(),
|
||||
)
|
||||
|
||||
onConfirmCaptor.value(false)
|
||||
onConfirmCaptor.value(
|
||||
PermissionPromptResponse(
|
||||
isPermissionsGranted = false,
|
||||
isPrivateModeGranted = false,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(GeckoResult.deny(), result)
|
||||
var nativePermissionPromptResponse: NativePermissionPromptResponse? = null
|
||||
result!!.accept {
|
||||
nativePermissionPromptResponse = it
|
||||
}
|
||||
|
||||
shadowOf(getMainLooper()).idle()
|
||||
assertFalse(nativePermissionPromptResponse!!.isPermissionsGranted!!)
|
||||
assertFalse(nativePermissionPromptResponse!!.isPrivateModeGranted!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package mozilla.components.browser.state.state.extension
|
||||
|
||||
import mozilla.components.concept.engine.webextension.PermissionPromptResponse
|
||||
import mozilla.components.concept.engine.webextension.WebExtension
|
||||
import mozilla.components.concept.engine.webextension.WebExtensionInstallException
|
||||
|
||||
@ -43,19 +44,19 @@ sealed class WebExtensionPromptRequest {
|
||||
*/
|
||||
sealed class Permissions(
|
||||
override val extension: WebExtension,
|
||||
open val onConfirm: (Boolean) -> Unit,
|
||||
) : AfterInstallation(extension) {
|
||||
/**
|
||||
* Value type that represents a request for a required permissions prompt.
|
||||
* @property extension The [WebExtension] that requested the dialog to be shown.
|
||||
* @property permissions The permissions to list in the dialog.
|
||||
* @property onConfirm A callback indicating whether the permissions were granted or not.
|
||||
* @property onConfirm A callback indicating the prompt has been confirmed and pass
|
||||
* [PermissionPromptResponse] result.
|
||||
*/
|
||||
data class Required(
|
||||
override val extension: WebExtension,
|
||||
val permissions: List<String>,
|
||||
override val onConfirm: (Boolean) -> Unit,
|
||||
) : Permissions(extension, onConfirm)
|
||||
val onConfirm: (PermissionPromptResponse) -> Unit,
|
||||
) : Permissions(extension)
|
||||
|
||||
/**
|
||||
* Value type that represents a request for an optional permissions prompt.
|
||||
@ -66,8 +67,8 @@ sealed class WebExtensionPromptRequest {
|
||||
data class Optional(
|
||||
override val extension: WebExtension,
|
||||
val permissions: List<String>,
|
||||
override val onConfirm: (Boolean) -> Unit,
|
||||
) : Permissions(extension, onConfirm)
|
||||
val onConfirm: (Boolean) -> Unit,
|
||||
) : Permissions(extension)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -519,6 +519,15 @@ enum class EnableSource(val id: Int) {
|
||||
APP_SUPPORT(1 shl 1),
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds all the information which the user has submitted
|
||||
* as part of a confirmation of a permissions prompt request.
|
||||
*/
|
||||
data class PermissionPromptResponse(
|
||||
val isPermissionsGranted: Boolean,
|
||||
val isPrivateModeGranted: Boolean = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* Flags to check for different reasons why an extension is disabled.
|
||||
*/
|
||||
|
@ -115,13 +115,13 @@ interface WebExtensionDelegate {
|
||||
*
|
||||
* @param extension the extension being installed. The required permissions can be accessed using
|
||||
* [WebExtension.getMetadata] and [Metadata.requiredPermissions]/[Metadata.requiredOrigins]/.
|
||||
* @param onPermissionsGranted A callback to indicate whether the user has granted the [extension] permissions.
|
||||
* @param onConfirm A callback to indicate the user's selection on the prompt.
|
||||
* @return whether or not installation should process i.e. the permissions have been granted.
|
||||
*/
|
||||
fun onInstallPermissionRequest(
|
||||
extension: WebExtension,
|
||||
permissions: List<String>,
|
||||
onPermissionsGranted: (Boolean) -> Unit,
|
||||
onConfirm: (PermissionPromptResponse) -> Unit,
|
||||
) = Unit
|
||||
|
||||
/**
|
||||
|
@ -20,9 +20,7 @@ import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.widget.AppCompatCheckBox
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.R
|
||||
@ -46,7 +44,7 @@ class AddonInstallationDialogFragment : AddonDialogFragment() {
|
||||
/**
|
||||
* A lambda called when the confirm button is clicked.
|
||||
*/
|
||||
var onConfirmButtonClicked: ((Addon, Boolean) -> Unit)? = null
|
||||
var onConfirmButtonClicked: ((Addon) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* A lambda called when the dialog is dismissed.
|
||||
@ -54,7 +52,6 @@ class AddonInstallationDialogFragment : AddonDialogFragment() {
|
||||
var onDismissed: (() -> Unit)? = null
|
||||
|
||||
internal val addon get() = requireNotNull(safeArguments.getParcelableCompat(KEY_INSTALLED_ADDON, Addon::class.java))
|
||||
private var allowPrivateBrowsing: Boolean = false
|
||||
|
||||
internal val confirmButtonRadius
|
||||
get() =
|
||||
@ -153,18 +150,9 @@ class AddonInstallationDialogFragment : AddonDialogFragment() {
|
||||
|
||||
loadIcon(addon = addon, iconView = binding.icon)
|
||||
|
||||
val allowedInPrivateBrowsing = rootView.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
if (addon.incognito == Addon.Incognito.NOT_ALLOWED) {
|
||||
allowedInPrivateBrowsing.isVisible = false
|
||||
} else {
|
||||
allowedInPrivateBrowsing.setOnCheckedChangeListener { _, isChecked ->
|
||||
allowPrivateBrowsing = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
val confirmButton = rootView.findViewById<Button>(R.id.confirm_button)
|
||||
confirmButton.setOnClickListener {
|
||||
onConfirmButtonClicked?.invoke(addon, allowPrivateBrowsing)
|
||||
onConfirmButtonClicked?.invoke(addon)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@ -223,7 +211,7 @@ class AddonInstallationDialogFragment : AddonDialogFragment() {
|
||||
shouldWidthMatchParent = true,
|
||||
),
|
||||
onDismissed: (() -> Unit)? = null,
|
||||
onConfirmButtonClicked: ((Addon, Boolean) -> Unit)? = null,
|
||||
onConfirmButtonClicked: ((Addon) -> Unit)? = null,
|
||||
): AddonInstallationDialogFragment {
|
||||
val fragment = AddonInstallationDialogFragment()
|
||||
val arguments = fragment.arguments ?: Bundle()
|
||||
|
@ -20,7 +20,9 @@ import android.widget.Button
|
||||
import android.widget.LinearLayout.LayoutParams
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.widget.AppCompatCheckBox
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import mozilla.components.feature.addons.Addon
|
||||
@ -43,9 +45,10 @@ private const val DEFAULT_VALUE = Int.MAX_VALUE
|
||||
class PermissionsDialogFragment : AddonDialogFragment() {
|
||||
|
||||
/**
|
||||
* A lambda called when the allow button is clicked.
|
||||
* A lambda called when the allow button is clicked which contains the [Addon] and
|
||||
* whether the addon is allowed in private browsing mode.
|
||||
*/
|
||||
var onPositiveButtonClicked: ((Addon) -> Unit)? = null
|
||||
var onPositiveButtonClicked: ((Addon, Boolean) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* A lambda called when the deny button is clicked.
|
||||
@ -160,6 +163,7 @@ class PermissionsDialogFragment : AddonDialogFragment() {
|
||||
val permissionsRecyclerView = rootView.findViewById<RecyclerView>(R.id.permissions)
|
||||
val positiveButton = rootView.findViewById<Button>(R.id.allow_button)
|
||||
val negativeButton = rootView.findViewById<Button>(R.id.deny_button)
|
||||
val allowedInPrivateBrowsing = rootView.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
|
||||
permissionsRecyclerView.adapter = RequiredPermissionsAdapter(listPermissions)
|
||||
permissionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||
@ -169,8 +173,12 @@ class PermissionsDialogFragment : AddonDialogFragment() {
|
||||
negativeButton.text = requireContext().getString(R.string.mozac_feature_addons_permissions_dialog_deny)
|
||||
}
|
||||
|
||||
if (addon.incognito == Addon.Incognito.NOT_ALLOWED) {
|
||||
allowedInPrivateBrowsing.isVisible = false
|
||||
}
|
||||
|
||||
positiveButton.setOnClickListener {
|
||||
onPositiveButtonClicked?.invoke(addon)
|
||||
onPositiveButtonClicked?.invoke(addon, allowedInPrivateBrowsing.isChecked)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@ -244,7 +252,7 @@ class PermissionsDialogFragment : AddonDialogFragment() {
|
||||
gravity = Gravity.BOTTOM,
|
||||
shouldWidthMatchParent = true,
|
||||
),
|
||||
onPositiveButtonClicked: ((Addon) -> Unit)? = null,
|
||||
onPositiveButtonClicked: ((Addon, Boolean) -> Unit)? = null,
|
||||
onNegativeButtonClicked: (() -> Unit)? = null,
|
||||
): PermissionsDialogFragment {
|
||||
val fragment = PermissionsDialogFragment()
|
||||
|
@ -52,24 +52,12 @@
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="@string/mozac_feature_addons_installed_dialog_description_2" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/allow_in_private_browsing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/description"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/mozac_feature_addons_settings_allow_in_private_browsing"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/confirm_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/allow_in_private_browsing"
|
||||
android:layout_below="@id/description"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
|
@ -2,11 +2,12 @@
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:windowBackground"
|
||||
@ -67,12 +68,25 @@
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/allow_in_private_browsing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/permissions"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/mozac_feature_addons_settings_allow_in_private_browsing"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/deny_button"
|
||||
style="?android:attr/borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/permissions"
|
||||
android:layout_below="@id/allow_in_private_browsing"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_toStartOf="@id/allow_button"
|
||||
android:text="@string/mozac_feature_addons_permissions_dialog_cancel"
|
||||
@ -83,7 +97,7 @@
|
||||
android:id="@+id/allow_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/permissions"
|
||||
android:layout_below="@id/allow_in_private_browsing"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
|
@ -6,10 +6,7 @@ package mozilla.components.feature.addons.ui
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatCheckBox
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
@ -19,7 +16,6 @@ import mozilla.components.support.test.mock
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import mozilla.components.support.utils.ext.getParcelableCompat
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
@ -54,53 +50,15 @@ class AddonInstallationDialogFragmentTest {
|
||||
val name = addon.translateName(testContext)
|
||||
val titleTextView = dialog.findViewById<TextView>(R.id.title)
|
||||
val description = dialog.findViewById<TextView>(R.id.description)
|
||||
val allowedInPrivateBrowsing = dialog.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
|
||||
assertTrue(titleTextView.text.contains(name))
|
||||
assertTrue(description.text.contains(name))
|
||||
assertTrue(allowedInPrivateBrowsing.isVisible)
|
||||
assertTrue(allowedInPrivateBrowsing.text.contains(testContext.getString(R.string.mozac_feature_addons_settings_allow_in_private_browsing)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on confirm dialog buttons notifies lambda with private browsing boolean`() {
|
||||
val addon = Addon("id", translatableName = mapOf(Addon.DEFAULT_LOCALE to "my_addon"))
|
||||
|
||||
val fragment = createAddonInstallationDialogFragment(addon)
|
||||
var confirmationWasExecuted = false
|
||||
var allowInPrivateBrowsing = false
|
||||
|
||||
fragment.onConfirmButtonClicked = { _, allow ->
|
||||
confirmationWasExecuted = true
|
||||
allowInPrivateBrowsing = allow
|
||||
}
|
||||
|
||||
doReturn(testContext).`when`(fragment).requireContext()
|
||||
|
||||
val dialog = fragment.onCreateDialog(null)
|
||||
dialog.show()
|
||||
val confirmButton = dialog.findViewById<Button>(R.id.confirm_button)
|
||||
val allowedInPrivateBrowsing = dialog.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
confirmButton.performClick()
|
||||
assertTrue(confirmationWasExecuted)
|
||||
assertFalse(allowInPrivateBrowsing)
|
||||
|
||||
dialog.show()
|
||||
allowedInPrivateBrowsing.performClick()
|
||||
confirmButton.performClick()
|
||||
assertTrue(confirmationWasExecuted)
|
||||
assertTrue(allowInPrivateBrowsing)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dismissing the dialog notifies nothing`() {
|
||||
val addon = Addon("id", translatableName = mapOf(Addon.DEFAULT_LOCALE to "my_addon"))
|
||||
val fragment = createAddonInstallationDialogFragment(addon)
|
||||
var confirmationWasExecuted = false
|
||||
|
||||
fragment.onConfirmButtonClicked = { _, _ ->
|
||||
confirmationWasExecuted = true
|
||||
}
|
||||
|
||||
doReturn(testContext).`when`(fragment).requireContext()
|
||||
|
||||
@ -109,7 +67,6 @@ class AddonInstallationDialogFragmentTest {
|
||||
val dialog = fragment.onCreateDialog(null)
|
||||
dialog.show()
|
||||
fragment.onDismiss(mock())
|
||||
assertFalse(confirmationWasExecuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -159,30 +116,6 @@ class AddonInstallationDialogFragmentTest {
|
||||
verify(fragmentTransaction).commitAllowingStateLoss()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hide private browsing checkbox when the add-on does not allow running in private windows`() {
|
||||
val addon = Addon(
|
||||
"id",
|
||||
translatableName = mapOf(Addon.DEFAULT_LOCALE to "my_addon"),
|
||||
permissions = listOf("privacy", "<all_urls>", "tabs"),
|
||||
incognito = Addon.Incognito.NOT_ALLOWED,
|
||||
)
|
||||
val fragment = createAddonInstallationDialogFragment(addon)
|
||||
assertSame(addon, fragment.arguments?.getParcelableCompat(KEY_INSTALLED_ADDON, Addon::class.java))
|
||||
|
||||
doReturn(testContext).`when`(fragment).requireContext()
|
||||
val dialog = fragment.onCreateDialog(null)
|
||||
dialog.show()
|
||||
val name = addon.translateName(testContext)
|
||||
val titleTextView = dialog.findViewById<TextView>(R.id.title)
|
||||
val description = dialog.findViewById<TextView>(R.id.description)
|
||||
val allowedInPrivateBrowsing = dialog.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
|
||||
assertTrue(titleTextView.text.contains(name))
|
||||
assertTrue(description.text.contains(name))
|
||||
assertFalse(allowedInPrivateBrowsing.isVisible)
|
||||
}
|
||||
|
||||
private fun createAddonInstallationDialogFragment(
|
||||
addon: Addon,
|
||||
promptsStyling: AddonDialogFragment.PromptsStyling? = null,
|
||||
|
@ -8,6 +8,8 @@ import android.view.Gravity.TOP
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatCheckBox
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -17,9 +19,11 @@ import mozilla.components.feature.addons.R
|
||||
import mozilla.components.feature.addons.ui.AddonDialogFragment.PromptsStyling
|
||||
import mozilla.components.support.test.mock
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import mozilla.components.support.utils.ext.getParcelableCompat
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@ -50,12 +54,15 @@ class PermissionsDialogFragmentTest {
|
||||
val recyclerAdapter = permissionsRecyclerView.adapter!! as RequiredPermissionsAdapter
|
||||
val permissionList = fragment.buildPermissionsList()
|
||||
val optionalOrRequiredText = fragment.buildOptionalOrRequiredText(hasPermissions = permissionList.isNotEmpty())
|
||||
val allowedInPrivateBrowsing =
|
||||
dialog.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
|
||||
assertTrue(titleTextView.text.contains(name))
|
||||
assertTrue(optionalOrRequiredText.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
assertTrue(permissionList.contains(testContext.getString(R.string.mozac_feature_addons_permissions_privacy_description)))
|
||||
assertTrue(permissionList.contains(testContext.getString(R.string.mozac_feature_addons_permissions_all_urls_description)))
|
||||
assertTrue(permissionList.contains(testContext.getString(R.string.mozac_feature_addons_permissions_tabs_description)))
|
||||
assertTrue(allowedInPrivateBrowsing.isVisible)
|
||||
|
||||
assertTrue(optionalOrRequiredTextView.text.contains(testContext.getString(R.string.mozac_feature_addons_permissions_dialog_subtitle)))
|
||||
Assert.assertNotNull(recyclerAdapter)
|
||||
@ -73,7 +80,7 @@ class PermissionsDialogFragmentTest {
|
||||
var allowedWasExecuted = false
|
||||
var denyWasExecuted = false
|
||||
|
||||
fragment.onPositiveButtonClicked = {
|
||||
fragment.onPositiveButtonClicked = { _, _ ->
|
||||
allowedWasExecuted = true
|
||||
}
|
||||
|
||||
@ -220,6 +227,37 @@ class PermissionsDialogFragmentTest {
|
||||
assertEquals(denyButton.text, testContext.getString(R.string.mozac_feature_addons_permissions_dialog_deny))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hide private browsing checkbox when the add-on does not allow running in private windows`() {
|
||||
val permissions = listOf("privacy", "<all_urls>", "tabs")
|
||||
val addon = Addon(
|
||||
"id",
|
||||
translatableName = mapOf(Addon.DEFAULT_LOCALE to "my_addon"),
|
||||
permissions = permissions,
|
||||
incognito = Addon.Incognito.NOT_ALLOWED,
|
||||
)
|
||||
val fragment = createPermissionsDialogFragment(addon, permissions)
|
||||
|
||||
assertSame(
|
||||
addon,
|
||||
fragment.arguments?.getParcelableCompat(KEY_ADDON, Addon::class.java),
|
||||
)
|
||||
|
||||
doReturn(testContext).`when`(fragment).requireContext()
|
||||
|
||||
val dialog = fragment.onCreateDialog(null)
|
||||
|
||||
dialog.show()
|
||||
|
||||
val name = addon.translateName(testContext)
|
||||
val titleTextView = dialog.findViewById<TextView>(R.id.title)
|
||||
val allowedInPrivateBrowsing =
|
||||
dialog.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
|
||||
assertTrue(titleTextView.text.contains(name))
|
||||
assertFalse(allowedInPrivateBrowsing.isVisible)
|
||||
}
|
||||
|
||||
private fun createPermissionsDialogFragment(
|
||||
addon: Addon,
|
||||
permissions: List<String>,
|
||||
|
@ -28,6 +28,7 @@ import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.concept.engine.webextension.Action
|
||||
import mozilla.components.concept.engine.webextension.ActionHandler
|
||||
import mozilla.components.concept.engine.webextension.PermissionPromptResponse
|
||||
import mozilla.components.concept.engine.webextension.TabHandler
|
||||
import mozilla.components.concept.engine.webextension.WebExtension
|
||||
import mozilla.components.concept.engine.webextension.WebExtensionDelegate
|
||||
@ -295,14 +296,14 @@ object WebExtensionSupport {
|
||||
override fun onInstallPermissionRequest(
|
||||
extension: WebExtension,
|
||||
permissions: List<String>,
|
||||
onPermissionsGranted: (Boolean) -> Unit,
|
||||
onConfirm: (PermissionPromptResponse) -> Unit,
|
||||
) {
|
||||
store.dispatch(
|
||||
WebExtensionAction.UpdatePromptRequestWebExtensionAction(
|
||||
WebExtensionPromptRequest.AfterInstallation.Permissions.Required(
|
||||
extension,
|
||||
permissions,
|
||||
onPermissionsGranted,
|
||||
onConfirm,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -23,6 +23,7 @@ import mozilla.components.concept.engine.webextension.Action
|
||||
import mozilla.components.concept.engine.webextension.ActionHandler
|
||||
import mozilla.components.concept.engine.webextension.Incognito
|
||||
import mozilla.components.concept.engine.webextension.Metadata
|
||||
import mozilla.components.concept.engine.webextension.PermissionPromptResponse
|
||||
import mozilla.components.concept.engine.webextension.TabHandler
|
||||
import mozilla.components.concept.engine.webextension.WebExtension
|
||||
import mozilla.components.concept.engine.webextension.WebExtensionDelegate
|
||||
@ -443,7 +444,7 @@ class WebExtensionSupportTest {
|
||||
val store = spy(BrowserStore())
|
||||
val engine: Engine = mock()
|
||||
val ext: WebExtension = mock()
|
||||
val onPermissionsGranted: ((Boolean) -> Unit) = mock()
|
||||
val onPermissionsGranted: ((PermissionPromptResponse) -> Unit) = mock()
|
||||
val permissions = listOf("permissions")
|
||||
|
||||
val delegateCaptor = argumentCaptor<WebExtensionDelegate>()
|
||||
|
@ -68,10 +68,6 @@ class AddonsFragment : Fragment(), AddonsManagerAdapterDelegate {
|
||||
findPreviousPermissionDialogFragment()?.let { dialog ->
|
||||
dialog.onPositiveButtonClicked = onConfirmPermissionButtonClicked
|
||||
}
|
||||
|
||||
findPreviousInstallationDialogFragment()?.let { dialog ->
|
||||
dialog.onConfirmButtonClicked = onConfirmInstallationButtonClicked
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindRecyclerView(rootView: View) {
|
||||
@ -174,7 +170,6 @@ class AddonsFragment : Fragment(), AddonsManagerAdapterDelegate {
|
||||
}
|
||||
val dialog = AddonInstallationDialogFragment.newInstance(
|
||||
addon = addon,
|
||||
onConfirmButtonClicked = onConfirmInstallationButtonClicked,
|
||||
)
|
||||
|
||||
if (!isAlreadyADialogCreated() && isAdded) {
|
||||
@ -182,16 +177,7 @@ class AddonsFragment : Fragment(), AddonsManagerAdapterDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private val onConfirmInstallationButtonClicked: ((Addon, Boolean) -> Unit) = { addon, allowInPrivateBrowsing ->
|
||||
if (allowInPrivateBrowsing) {
|
||||
requireContext().components.addonManager.setAddonAllowedInPrivateBrowsing(
|
||||
addon,
|
||||
allowInPrivateBrowsing,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val onConfirmPermissionButtonClicked: ((Addon) -> Unit) = { addon ->
|
||||
private val onConfirmPermissionButtonClicked: ((Addon, Boolean) -> Unit) = { addon, _ ->
|
||||
val includedBinding = OverlayAddOnProgressBinding.bind(binding.addonProgressOverlay.addonProgressOverlay)
|
||||
|
||||
includedBinding.root.visibility = View.VISIBLE
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package org.mozilla.fenix.ui
|
||||
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.R
|
||||
@ -130,14 +131,14 @@ class SettingsAddonsTest : TestSetup() {
|
||||
|
||||
// TestRail link: https://mozilla.testrail.io/index.php?/cases/view/561594
|
||||
@SmokeTest
|
||||
@Ignore("Intermittent test https://bugzilla.mozilla.org/show_bug.cgi?id=1827180")
|
||||
@Test
|
||||
fun verifyUBlockWorksInPrivateModeTest() {
|
||||
TestHelper.appContext.settings().shouldShowCookieBannersCFR = false
|
||||
val addonName = "uBlock Origin"
|
||||
|
||||
addonsMenu {
|
||||
installAddon(addonName, activityTestRule)
|
||||
selectAllowInPrivateBrowsing()
|
||||
installAddonInPrivateMode(addonName, activityTestRule)
|
||||
closeAddonInstallCompletePrompt()
|
||||
}.goBack {
|
||||
}.openContextMenuOnSponsoredShortcut("Top Articles") {
|
||||
|
@ -144,7 +144,6 @@ class SettingsSubMenuAddonsManagerRobot {
|
||||
withParent(instanceOf(RelativeLayout::class.java)),
|
||||
hasSibling(withText("$addonName has been added to $appName")),
|
||||
hasSibling(withText("Access $addonName from the $appName menu.")),
|
||||
hasSibling(withText("Allow in private browsing")),
|
||||
),
|
||||
)
|
||||
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
@ -242,6 +241,18 @@ class SettingsSubMenuAddonsManagerRobot {
|
||||
}
|
||||
}
|
||||
|
||||
fun installAddonInPrivateMode(addonName: String, activityTestRule: HomeActivityIntentTestRule) {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openAddonsManagerMenu {
|
||||
clickInstallAddon(addonName)
|
||||
verifyAddonPermissionPrompt(addonName)
|
||||
selectAllowInPrivateBrowsing()
|
||||
acceptPermissionToInstallAddon()
|
||||
verifyAddonInstallCompleted(addonName, activityTestRule)
|
||||
}
|
||||
}
|
||||
|
||||
class Transition {
|
||||
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
|
||||
Log.i(TAG, "goBack: Trying to click navigate up toolbar button")
|
||||
|
@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.mapNotNull
|
||||
import mozilla.components.browser.state.action.WebExtensionAction
|
||||
import mozilla.components.browser.state.state.extension.WebExtensionPromptRequest
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.webextension.PermissionPromptResponse
|
||||
import mozilla.components.concept.engine.webextension.WebExtensionInstallException
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.AddonManager
|
||||
@ -29,7 +30,6 @@ import mozilla.components.ui.widgets.withCenterAlignedButtons
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.theme.ThemeManager
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Feature implementation for handling [WebExtensionPromptRequest] and showing the respective UI.
|
||||
@ -133,7 +133,7 @@ class WebExtensionPromptFeature(
|
||||
|
||||
// If we don't have any promptable permissions, just proceed.
|
||||
if (shouldGrantWithoutPrompt) {
|
||||
handlePermissions(promptRequest, granted = true)
|
||||
handlePermissions(promptRequest, granted = true, privateBrowsingAllowed = false)
|
||||
return
|
||||
}
|
||||
|
||||
@ -247,8 +247,20 @@ class WebExtensionPromptFeature(
|
||||
confirmButtonRadius =
|
||||
(context.resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat(),
|
||||
),
|
||||
onPositiveButtonClicked = { handlePermissions(promptRequest, granted = true) },
|
||||
onNegativeButtonClicked = { handlePermissions(promptRequest, granted = false) },
|
||||
onPositiveButtonClicked = { _, privateBrowsingAllowed ->
|
||||
handlePermissions(
|
||||
promptRequest,
|
||||
granted = true,
|
||||
privateBrowsingAllowed,
|
||||
)
|
||||
},
|
||||
onNegativeButtonClicked = {
|
||||
handlePermissions(
|
||||
promptRequest,
|
||||
granted = false,
|
||||
privateBrowsingAllowed = false,
|
||||
)
|
||||
},
|
||||
)
|
||||
dialog.show(
|
||||
fragmentManager,
|
||||
@ -258,38 +270,25 @@ class WebExtensionPromptFeature(
|
||||
|
||||
private fun tryToReAttachButtonHandlersToPreviousDialog() {
|
||||
findPreviousPermissionDialogFragment()?.let { dialog ->
|
||||
dialog.onPositiveButtonClicked = { addon ->
|
||||
dialog.onPositiveButtonClicked = { addon, privateBrowsingAllowed ->
|
||||
store.state.webExtensionPromptRequest?.let { promptRequest ->
|
||||
if (promptRequest is WebExtensionPromptRequest.AfterInstallation.Permissions &&
|
||||
addon.id == promptRequest.extension.id
|
||||
) {
|
||||
handlePermissions(promptRequest, granted = true)
|
||||
handlePermissions(promptRequest, granted = true, privateBrowsingAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.onNegativeButtonClicked = {
|
||||
store.state.webExtensionPromptRequest?.let { promptRequest ->
|
||||
if (promptRequest is WebExtensionPromptRequest.AfterInstallation.Permissions) {
|
||||
handlePermissions(promptRequest, granted = false)
|
||||
handlePermissions(promptRequest, granted = false, privateBrowsingAllowed = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPreviousPostInstallationDialogFragment()?.let { dialog ->
|
||||
dialog.onConfirmButtonClicked = { addon, allowInPrivateBrowsing ->
|
||||
store.state.webExtensionPromptRequest?.let { promptRequest ->
|
||||
if (promptRequest is WebExtensionPromptRequest.AfterInstallation.PostInstallation &&
|
||||
addon.id == promptRequest.extension.id
|
||||
) {
|
||||
handlePostInstallationButtonClicked(
|
||||
allowInPrivateBrowsing = allowInPrivateBrowsing,
|
||||
context = WeakReference(context),
|
||||
addon = addon,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.onDismissed = {
|
||||
store.state.webExtensionPromptRequest?.let { _ ->
|
||||
consumePromptRequest()
|
||||
@ -301,8 +300,21 @@ class WebExtensionPromptFeature(
|
||||
private fun handlePermissions(
|
||||
promptRequest: WebExtensionPromptRequest.AfterInstallation.Permissions,
|
||||
granted: Boolean,
|
||||
privateBrowsingAllowed: Boolean,
|
||||
) {
|
||||
promptRequest.onConfirm(granted)
|
||||
when (promptRequest) {
|
||||
is WebExtensionPromptRequest.AfterInstallation.Permissions.Optional -> {
|
||||
promptRequest.onConfirm(granted)
|
||||
}
|
||||
|
||||
is WebExtensionPromptRequest.AfterInstallation.Permissions.Required -> {
|
||||
val response = PermissionPromptResponse(
|
||||
isPermissionsGranted = granted,
|
||||
isPrivateModeGranted = privateBrowsingAllowed,
|
||||
)
|
||||
promptRequest.onConfirm(response)
|
||||
}
|
||||
}
|
||||
consumePromptRequest()
|
||||
}
|
||||
|
||||
@ -332,13 +344,6 @@ class WebExtensionPromptFeature(
|
||||
|
||||
private fun showPostInstallationDialog(addon: Addon) {
|
||||
if (!isInstallationInProgress && !hasExistingAddonPostInstallationDialogFragment()) {
|
||||
// Fragment may not be attached to the context anymore during onConfirmButtonClicked handling,
|
||||
// but we still want to be able to process user selection of the 'allowInPrivateBrowsing' pref.
|
||||
// This is a best-effort attempt to do so - retain a weak reference to the application context
|
||||
// (to avoid a leak), which we attempt to use to access addonManager.
|
||||
// See https://github.com/mozilla-mobile/fenix/issues/15816
|
||||
val weakApplicationContext: WeakReference<Context> = WeakReference(context)
|
||||
|
||||
val dialog = AddonInstallationDialogFragment.newInstance(
|
||||
addon = addon,
|
||||
promptsStyling = AddonDialogFragment.PromptsStyling(
|
||||
@ -358,35 +363,14 @@ class WebExtensionPromptFeature(
|
||||
onDismissed = {
|
||||
consumePromptRequest()
|
||||
},
|
||||
onConfirmButtonClicked = { _, allowInPrivateBrowsing ->
|
||||
handlePostInstallationButtonClicked(
|
||||
addon = addon,
|
||||
context = weakApplicationContext,
|
||||
allowInPrivateBrowsing = allowInPrivateBrowsing,
|
||||
)
|
||||
onConfirmButtonClicked = { _ ->
|
||||
consumePromptRequest()
|
||||
},
|
||||
)
|
||||
dialog.show(fragmentManager, POST_INSTALLATION_DIALOG_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostInstallationButtonClicked(
|
||||
context: WeakReference<Context>,
|
||||
allowInPrivateBrowsing: Boolean,
|
||||
addon: Addon,
|
||||
) {
|
||||
if (allowInPrivateBrowsing) {
|
||||
context.get()?.components?.addonManager?.setAddonAllowedInPrivateBrowsing(
|
||||
addon = addon,
|
||||
allowed = true,
|
||||
onSuccess = { updatedAddon ->
|
||||
onAddonChanged(updatedAddon)
|
||||
},
|
||||
)
|
||||
}
|
||||
consumePromptRequest()
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun showDialog(
|
||||
title: String,
|
||||
|
@ -2708,6 +2708,12 @@ package org.mozilla.geckoview {
|
||||
field @NonNull public final String version;
|
||||
}
|
||||
|
||||
public static class WebExtension.PermissionPromptResponse {
|
||||
ctor public PermissionPromptResponse(@Nullable Boolean, @Nullable Boolean);
|
||||
field @Nullable public final Boolean isPermissionsGranted;
|
||||
field @Nullable public final Boolean isPrivateModeGranted;
|
||||
}
|
||||
|
||||
@UiThread public static class WebExtension.Port {
|
||||
ctor protected Port();
|
||||
method public void disconnect();
|
||||
@ -2829,7 +2835,8 @@ package org.mozilla.geckoview {
|
||||
|
||||
@UiThread public static interface WebExtensionController.PromptDelegate {
|
||||
method @Deprecated @DeprecationSchedule(id="web-extension-required-permissions",version=133) @Nullable default public GeckoResult<AllowOrDeny> onInstallPrompt(@NonNull WebExtension);
|
||||
method @Nullable default public GeckoResult<AllowOrDeny> onInstallPrompt(@NonNull WebExtension, @NonNull String[], @NonNull String[]);
|
||||
method @Deprecated @DeprecationSchedule(id="web-extension-on-install-prompt",version=134) @Nullable default public GeckoResult<AllowOrDeny> onInstallPrompt(@NonNull WebExtension, @NonNull String[], @NonNull String[]);
|
||||
method @Nullable default public GeckoResult<WebExtension.PermissionPromptResponse> onInstallPromptRequest(@NonNull WebExtension, @NonNull String[], @NonNull String[]);
|
||||
method @Nullable default public GeckoResult<AllowOrDeny> onOptionalPrompt(@NonNull WebExtension, @NonNull String[], @NonNull String[]);
|
||||
method @Nullable default public GeckoResult<AllowOrDeny> onUpdatePrompt(@NonNull WebExtension, @NonNull WebExtension, @NonNull String[], @NonNull String[]);
|
||||
}
|
||||
|
@ -2589,6 +2589,7 @@ class NavigationDelegateTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateUntilTestEnd(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
|
@ -698,6 +698,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -777,7 +778,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun installWebExtension() {
|
||||
fun installWebExtensionOnInstallPrompt() {
|
||||
mainSession.loadUri("https://example.com")
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
@ -787,6 +788,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Remove test when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -846,6 +848,159 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
assertBodyBorderEqualTo("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun installWebExtension() {
|
||||
mainSession.loadUri("https://example.com")
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
// First let's check that the color of the border is empty before loading
|
||||
// the WebExtension
|
||||
assertBodyBorderEqualTo("")
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
override fun onInstallPromptRequest(
|
||||
extension: WebExtension,
|
||||
permissions: Array<out String>,
|
||||
origins: Array<out String>,
|
||||
): GeckoResult<PermissionPromptResponse>? {
|
||||
assertEquals(
|
||||
extension.metaData.description,
|
||||
"Adds a red border to all webpages matching example.com.",
|
||||
)
|
||||
assertEquals(extension.metaData.name, "Borderify")
|
||||
assertEquals(extension.metaData.version, "1.0")
|
||||
assertEquals(extension.isBuiltIn, false)
|
||||
assertEquals(extension.metaData.enabled, false)
|
||||
assertEquals(
|
||||
extension.metaData.signedState,
|
||||
WebExtension.SignedStateFlags.SIGNED,
|
||||
)
|
||||
assertEquals(
|
||||
extension.metaData.blocklistState,
|
||||
WebExtension.BlocklistStateFlags.NOT_BLOCKED,
|
||||
)
|
||||
assertEquals(extension.metaData.incognito, "spanning")
|
||||
return GeckoResult.fromValue(
|
||||
PermissionPromptResponse(
|
||||
true, // isPermissionsGranted
|
||||
false, // isPrivateModeGranted
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
val borderify = sessionRule.waitForResult(
|
||||
controller.install(
|
||||
"resource://android/assets/web_extensions/borderify.xpi",
|
||||
null,
|
||||
),
|
||||
)
|
||||
|
||||
mainSession.reload()
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
// Check that the WebExtension was applied by checking the border color
|
||||
assertBodyBorderEqualTo("red")
|
||||
assertFalse(borderify.metaData.allowedInPrivateBrowsing)
|
||||
|
||||
var list = extensionsMap(sessionRule.waitForResult(controller.list()))
|
||||
assertEquals(list.size, 2)
|
||||
assertTrue(list.containsKey(borderify.id))
|
||||
assertTrue(list.containsKey(RuntimeCreator.TEST_SUPPORT_EXTENSION_ID))
|
||||
|
||||
// Uninstall WebExtension and check again
|
||||
sessionRule.waitForResult(controller.uninstall(borderify))
|
||||
|
||||
list = extensionsMap(sessionRule.waitForResult(controller.list()))
|
||||
assertEquals(list.size, 1)
|
||||
assertTrue(list.containsKey(RuntimeCreator.TEST_SUPPORT_EXTENSION_ID))
|
||||
|
||||
mainSession.reload()
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
// Check that the WebExtension was not applied after being uninstalled
|
||||
assertBodyBorderEqualTo("")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Setting.List(Setting(key = Setting.Key.USE_PRIVATE_MODE, value = "true"))
|
||||
fun installWebExtensionAllowInPrivateMode() {
|
||||
mainSession.loadUri("https://example.com")
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
// First let's check that the color of the border is empty before loading
|
||||
// the WebExtension
|
||||
assertBodyBorderEqualTo("")
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
override fun onInstallPromptRequest(
|
||||
extension: WebExtension,
|
||||
permissions: Array<out String>,
|
||||
origins: Array<out String>,
|
||||
): GeckoResult<PermissionPromptResponse>? {
|
||||
assertEquals(
|
||||
extension.metaData.description,
|
||||
"Adds a red border to all webpages matching example.com.",
|
||||
)
|
||||
assertEquals(extension.metaData.name, "Borderify")
|
||||
assertEquals(extension.metaData.version, "1.0")
|
||||
assertEquals(extension.isBuiltIn, false)
|
||||
assertEquals(extension.metaData.enabled, false)
|
||||
assertEquals(
|
||||
extension.metaData.signedState,
|
||||
WebExtension.SignedStateFlags.SIGNED,
|
||||
)
|
||||
assertEquals(
|
||||
extension.metaData.blocklistState,
|
||||
WebExtension.BlocklistStateFlags.NOT_BLOCKED,
|
||||
)
|
||||
assertEquals(extension.metaData.incognito, "spanning")
|
||||
|
||||
return GeckoResult.fromValue(
|
||||
PermissionPromptResponse(
|
||||
true, // isPermissionsGranted
|
||||
true, // isPrivateModeGranted
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
val borderify = sessionRule.waitForResult(
|
||||
controller.install(
|
||||
"resource://android/assets/web_extensions/borderify.xpi",
|
||||
null,
|
||||
),
|
||||
)
|
||||
|
||||
mainSession.reload()
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
// Check that the WebExtension was applied by checking the border color
|
||||
assertBodyBorderEqualTo("red")
|
||||
assertTrue(mainSession.settings.usePrivateMode)
|
||||
assertTrue(borderify.metaData.allowedInPrivateBrowsing)
|
||||
|
||||
var list = extensionsMap(sessionRule.waitForResult(controller.list()))
|
||||
assertEquals(list.size, 2)
|
||||
assertTrue(list.containsKey(borderify.id))
|
||||
assertTrue(list.containsKey(RuntimeCreator.TEST_SUPPORT_EXTENSION_ID))
|
||||
|
||||
// Uninstall WebExtension and check again
|
||||
sessionRule.waitForResult(controller.uninstall(borderify))
|
||||
|
||||
list = extensionsMap(sessionRule.waitForResult(controller.list()))
|
||||
assertEquals(list.size, 1)
|
||||
assertTrue(list.containsKey(RuntimeCreator.TEST_SUPPORT_EXTENSION_ID))
|
||||
|
||||
mainSession.reload()
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
// Check that the WebExtension was not applied after being uninstalled
|
||||
assertBodyBorderEqualTo("")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Setting.List(Setting(key = Setting.Key.USE_PRIVATE_MODE, value = "true"))
|
||||
fun runInPrivateBrowsing() {
|
||||
@ -857,6 +1012,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -940,6 +1096,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1015,6 +1172,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 2)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1070,6 +1228,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
) {
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 0)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1130,6 +1289,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
)
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1228,6 +1388,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
fun corruptFileErrorWillNotReturnAnWebExtensionWithoutId() {
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 0)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1292,6 +1453,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1902,6 +2064,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
)
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1980,6 +2143,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -1994,6 +2158,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -2909,6 +3074,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -2970,6 +3136,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3011,6 +3178,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3059,6 +3227,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3134,6 +3303,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3191,6 +3361,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3267,6 +3438,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
fun cancelInstallFailsAfterInstalled() {
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3308,6 +3480,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3370,6 +3543,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3529,6 +3703,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3608,6 +3783,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -3901,6 +4077,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
var addonId = ""
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
@ -4041,6 +4218,7 @@ class WebExtensionTest : BaseSessionTest() {
|
||||
|
||||
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
@Deprecated("Update to the new API when addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374")
|
||||
override fun onInstallPrompt(
|
||||
extension: WebExtension,
|
||||
permissions: Array<String>,
|
||||
|
@ -2965,4 +2965,29 @@ public class WebExtension {
|
||||
this.initData = initData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds all the information which the user has submited as part of a confirmation of a
|
||||
* permissions prompt request.
|
||||
*/
|
||||
public static class PermissionPromptResponse {
|
||||
/** Whether the user granted permissions or not. */
|
||||
@Nullable public final Boolean isPermissionsGranted;
|
||||
|
||||
/** Whether the user granted access in private mode or not. */
|
||||
@Nullable public final Boolean isPrivateModeGranted;
|
||||
|
||||
/**
|
||||
* Creates a new PermissionPromptResponse with the given fields.
|
||||
*
|
||||
* @param isPermissionsGranted Whether the user granted permissions or not.
|
||||
* @param isPrivateModeGranted Whether the user granted access in private mode or not.
|
||||
*/
|
||||
public PermissionPromptResponse(
|
||||
final @Nullable Boolean isPermissionsGranted,
|
||||
final @Nullable Boolean isPrivateModeGranted) {
|
||||
this.isPermissionsGranted = isPermissionsGranted;
|
||||
this.isPrivateModeGranted = isPrivateModeGranted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,9 +266,9 @@ public class WebExtensionController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new extension is being installed. This is intended as an opportunity for
|
||||
* the app to prompt the user for the permissions required by this extension.
|
||||
*
|
||||
* @deprecated Please use onInstallPromptRequest instead. Called whenever a new extension is
|
||||
* being installed. This is intended as an opportunity for the app to prompt the user for
|
||||
* the permissions required by this extension.
|
||||
* @param extension The {@link WebExtension} that is about to be installed. You can use {@link
|
||||
* WebExtension#metaData} to gather information about this extension when building the user
|
||||
* prompt dialog.
|
||||
@ -280,6 +280,8 @@ public class WebExtensionController {
|
||||
* DENY}.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
@DeprecationSchedule(id = "web-extension-on-install-prompt", version = 134)
|
||||
default GeckoResult<AllowOrDeny> onInstallPrompt(
|
||||
@NonNull final WebExtension extension,
|
||||
@NonNull final String[] permissions,
|
||||
@ -287,6 +289,26 @@ public class WebExtensionController {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new extension is being installed. This is intended as an opportunity for
|
||||
* the app to prompt the user for the permissions required by this extension.
|
||||
*
|
||||
* @param extension The {@link WebExtension} that is about to be installed. You can use {@link
|
||||
* WebExtension#metaData} to gather information about this extension when building the user
|
||||
* prompt dialog.
|
||||
* @param permissions The list of permissions that are granted during installation.
|
||||
* @param origins The list of origins that are granted during installation.
|
||||
* @return A {@link GeckoResult} that completes with a {@link
|
||||
* WebExtension.PermissionPromptResponse} containing all the details from the user response.
|
||||
*/
|
||||
@Nullable
|
||||
default GeckoResult<WebExtension.PermissionPromptResponse> onInstallPromptRequest(
|
||||
@NonNull final WebExtension extension,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final String[] origins) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever an updated extension has new permissions. This is intended as an opportunity
|
||||
* for the app to prompt the user for the new permissions required by this extension.
|
||||
@ -632,7 +654,8 @@ public class WebExtensionController {
|
||||
*
|
||||
* <p>When calling this method, the GeckoView library will download the extension, validate its
|
||||
* manifest and signature, and give you an opportunity to verify its permissions through {@link
|
||||
* PromptDelegate#installPrompt}, you can use this method to prompt the user if appropriate.
|
||||
* PromptDelegate#onInstallPromptRequest}, you can use this method to prompt the user if
|
||||
* appropriate.
|
||||
*
|
||||
* @param uri URI to the extension's <code>.xpi</code> package. This can be a remote <code>https:
|
||||
* </code> URI or a local <code>file:</code> or <code>resource:</code> URI. Note: the app
|
||||
@ -646,7 +669,7 @@ public class WebExtensionController {
|
||||
* exceptionally with a {@link WebExtension.InstallException InstallException} that will
|
||||
* contain the relevant error code in {@link WebExtension.InstallException#code
|
||||
* InstallException#code}.
|
||||
* @see PromptDelegate#installPrompt
|
||||
* @see PromptDelegate#onInstallPromptRequest(WebExtension, String[], String[])
|
||||
* @see WebExtension.InstallException.ErrorCodes
|
||||
* @see WebExtension#metaData
|
||||
*/
|
||||
@ -683,9 +706,9 @@ public class WebExtensionController {
|
||||
*
|
||||
* <p>When calling this method, the GeckoView library will download the extension, validate its
|
||||
* manifest and signature, and give you an opportunity to verify its permissions through {@link
|
||||
* PromptDelegate#installPrompt}, you can use this method to prompt the user if appropriate. If
|
||||
* you are looking to provide an {@link InstallationMethod}, please use {@link
|
||||
* WebExtensionController#install(String, String)}
|
||||
* PromptDelegate#installPromptRequest(GeckoBundle, EventCallback)}, you can use this method to
|
||||
* prompt the user if appropriate. If you are looking to provide an {@link InstallationMethod},
|
||||
* please use {@link WebExtensionController#install(String, String)}
|
||||
*
|
||||
* @param uri URI to the extension's <code>.xpi</code> package. This can be a remote <code>https:
|
||||
* </code> URI or a local <code>file:</code> or <code>resource:</code> URI. Note: the app
|
||||
@ -698,7 +721,7 @@ public class WebExtensionController {
|
||||
* exceptionally with a {@link WebExtension.InstallException InstallException} that will
|
||||
* contain the relevant error code in {@link WebExtension.InstallException#code
|
||||
* InstallException#code}.
|
||||
* @see PromptDelegate#installPrompt
|
||||
* @see PromptDelegate#installPromptRequest
|
||||
* @see WebExtension.InstallException.ErrorCodes
|
||||
* @see WebExtension#metaData
|
||||
*/
|
||||
@ -997,7 +1020,7 @@ public class WebExtensionController {
|
||||
* @return A {@link GeckoResult} that will complete when the update process finishes. If an update
|
||||
* is found and installed successfully, the GeckoResult will return the updated {@link
|
||||
* WebExtension}. If no update is available, null will be returned. If the updated extension
|
||||
* requires new permissions, the {@link PromptDelegate#installPrompt} will be called.
|
||||
* requires new permissions, the {@link PromptDelegate#installPromptRequest} will be called.
|
||||
* @see PromptDelegate#updatePrompt
|
||||
*/
|
||||
@AnyThread
|
||||
@ -1041,7 +1064,7 @@ public class WebExtensionController {
|
||||
Log.d(LOGTAG, "handleMessage " + event);
|
||||
|
||||
if ("GeckoView:WebExtension:InstallPrompt".equals(event)) {
|
||||
installPrompt(bundle, callback);
|
||||
installPromptRequest(bundle, callback);
|
||||
return;
|
||||
} else if ("GeckoView:WebExtension:UpdatePrompt".equals(event)) {
|
||||
updatePrompt(bundle, callback);
|
||||
@ -1166,7 +1189,7 @@ public class WebExtensionController {
|
||||
});
|
||||
}
|
||||
|
||||
private void installPrompt(final GeckoBundle message, final EventCallback callback) {
|
||||
private void installPromptRequest(final GeckoBundle message, final EventCallback callback) {
|
||||
final GeckoBundle extensionBundle = message.getBundle("extension");
|
||||
if (extensionBundle == null
|
||||
|| !extensionBundle.containsKey("webExtensionId")
|
||||
@ -1187,20 +1210,38 @@ public class WebExtensionController {
|
||||
return;
|
||||
}
|
||||
|
||||
final GeckoResult<AllowOrDeny> promptResponse =
|
||||
@SuppressWarnings("deprecation")
|
||||
final GeckoResult<AllowOrDeny> promptResponseDeprecated =
|
||||
mPromptDelegate.onInstallPrompt(
|
||||
extension, message.getStringArray("permissions"), message.getStringArray("origins"));
|
||||
if (promptResponse == null) {
|
||||
return;
|
||||
// To be deleted after addressing https://bugzilla.mozilla.org/show_bug.cgi?id=1919374
|
||||
// If we get null from onInstallPrompt this means nobody has implemented it, so we proceed to
|
||||
// call the new API onInstallPromptRequest.
|
||||
if (promptResponseDeprecated != null) {
|
||||
callback.resolveTo(
|
||||
promptResponseDeprecated.map(
|
||||
allowOrDeny -> {
|
||||
final GeckoBundle response = new GeckoBundle(2);
|
||||
response.putBoolean("allow", AllowOrDeny.ALLOW.equals(allowOrDeny));
|
||||
response.putBoolean("privateBrowsingAllowed", false);
|
||||
return response;
|
||||
}));
|
||||
} else {
|
||||
final GeckoResult<WebExtension.PermissionPromptResponse> promptResponse =
|
||||
mPromptDelegate.onInstallPromptRequest(
|
||||
extension, message.getStringArray("permissions"), message.getStringArray("origins"));
|
||||
if (promptResponse == null) {
|
||||
return;
|
||||
}
|
||||
callback.resolveTo(
|
||||
promptResponse.map(
|
||||
userResponse -> {
|
||||
final GeckoBundle response = new GeckoBundle(2);
|
||||
response.putBoolean("allow", userResponse.isPermissionsGranted);
|
||||
response.putBoolean("privateBrowsingAllowed", userResponse.isPrivateModeGranted);
|
||||
return response;
|
||||
}));
|
||||
}
|
||||
|
||||
callback.resolveTo(
|
||||
promptResponse.map(
|
||||
allowOrDeny -> {
|
||||
final GeckoBundle response = new GeckoBundle(1);
|
||||
response.putBoolean("allow", AllowOrDeny.ALLOW.equals(allowOrDeny));
|
||||
return response;
|
||||
}));
|
||||
}
|
||||
|
||||
private void updatePrompt(final GeckoBundle message, final EventCallback callback) {
|
||||
|
@ -15,10 +15,16 @@ exclude: true
|
||||
|
||||
## v133
|
||||
- Added [`GeckoSession.getWebCompatInfo`][133.1] that returns a `GeckoResult<JSONObject>` for web compatability information. ([bug 1917273]({{bugzilla}}1917273)).
|
||||
-Added [`isInteractiveWidgetDefaultResizesVisual`][133.2] to tell the preference value of "dom.interactive_widget_default_resizes_visual".
|
||||
-Added [`isInteractiveWidgetDefaultResizesVisual`][133.2] to tell the preference value of "dom.interactive_widget_default_resizes_visual".
|
||||
- Added [`WebExtension.PermissionPromptResponse`][133.3] Represents a response from `WebExtension` prompt request.
|
||||
- Added [`WebExtension.onInstallPromptRequest`][133.4] Delegate notified when install prompt needs to be shown.
|
||||
- ⚠️ [`WebExtensionController.PromptDelegate.onInstallPrompt`][133.5] is deprecated, and it will be deleted in version 134 see https://bugzilla.mozilla.org/show_bug.cgi?id=1919374.
|
||||
|
||||
[133.1]: {{javadoc_uri}}/GeckoSession.html#getWebCompatInfo()
|
||||
[133.2]: {{javadoc_uri}}/GeckoRuntime.html#isInteractiveWidgetDefaultResizesVisual()
|
||||
[133.3]: {{javadoc_uri}}/WebExtension.PermissionPromptResponse.html
|
||||
[133.4]: {{javadoc_uri}}/WebExtensionController.PromptDelegate.html#onInstallPromptRequest
|
||||
[133.5]: {{javadoc_uri}}/WebExtensionController.PromptDelegate.html#onInstallPrompt
|
||||
|
||||
## v132
|
||||
-Added [`getDisableShip`][132.1] to get the setting for Session History in Parent (SHIP)) and [`disableShip`][132.2] to set the status of SHIP on the `GeckoRuntimeSettings` builder.
|
||||
@ -1622,4 +1628,4 @@ to allow adding gecko profiler markers.
|
||||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String)
|
||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||
|
||||
[api-version]: 39fcf644fe97ddf83f54acd333e98062d1d1f0e7
|
||||
[api-version]: 926fb0231b94aa42486a1084d258e45e3d20bcab
|
||||
|
@ -143,11 +143,13 @@ class WebExtensionManager
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public GeckoResult<AllowOrDeny> onInstallPrompt(
|
||||
final @NonNull WebExtension extension,
|
||||
@NonNull String[] permissions,
|
||||
@NonNull String[] origins) {
|
||||
return GeckoResult.allow();
|
||||
public GeckoResult<WebExtension.PermissionPromptResponse> onInstallPromptRequest(
|
||||
@NonNull WebExtension extension, @NonNull String[] permissions, @NonNull String[] origins) {
|
||||
return GeckoResult.fromValue(
|
||||
new org.mozilla.geckoview.WebExtension.PermissionPromptResponse(
|
||||
true, // isPermissionsGranted
|
||||
true // isPrivateModeGranted
|
||||
));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -5,8 +5,9 @@
|
||||
import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
|
||||
import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
|
||||
|
||||
const PRIVATE_BROWSING_PERMISSION = {
|
||||
permissions: ["internal:privateBrowsingAllowed"],
|
||||
const PRIVATE_BROWSING_PERM_NAME = "internal:privateBrowsingAllowed";
|
||||
const PRIVATE_BROWSING_PERMS = {
|
||||
permissions: [PRIVATE_BROWSING_PERM_NAME],
|
||||
origins: [],
|
||||
};
|
||||
|
||||
@ -354,7 +355,13 @@ async function exportExtension(aAddon, aSourceURI) {
|
||||
disabledFlags.push("appVersionDisabled");
|
||||
}
|
||||
const baseURL = policy ? policy.getURL() : "";
|
||||
const privateBrowsingAllowed = policy ? policy.privateBrowsingAllowed : false;
|
||||
let privateBrowsingAllowed;
|
||||
if (policy) {
|
||||
privateBrowsingAllowed = policy.privateBrowsingAllowed;
|
||||
} else {
|
||||
const { permissions } = await lazy.ExtensionPermissions.get(aAddon.id);
|
||||
privateBrowsingAllowed = permissions.includes(PRIVATE_BROWSING_PERM_NAME);
|
||||
}
|
||||
|
||||
let updateDate;
|
||||
try {
|
||||
@ -520,7 +527,7 @@ class ExtensionPromptObserver {
|
||||
Services.obs.addObserver(this, "webextension-optional-permission-prompt");
|
||||
}
|
||||
|
||||
async permissionPrompt(aInstall, aAddon, aInfo) {
|
||||
async permissionPromptRequest(aInstall, aAddon, aInfo) {
|
||||
const { sourceURI } = aInstall;
|
||||
const { permissions } = aInfo;
|
||||
|
||||
@ -533,6 +540,14 @@ class ExtensionPromptObserver {
|
||||
});
|
||||
|
||||
if (response.allow) {
|
||||
if (response.privateBrowsingAllowed) {
|
||||
await lazy.ExtensionPermissions.add(aAddon.id, PRIVATE_BROWSING_PERMS);
|
||||
} else {
|
||||
await lazy.ExtensionPermissions.remove(
|
||||
aAddon.id,
|
||||
PRIVATE_BROWSING_PERMS
|
||||
);
|
||||
}
|
||||
aInfo.resolve();
|
||||
} else {
|
||||
aInfo.reject();
|
||||
@ -555,7 +570,7 @@ class ExtensionPromptObserver {
|
||||
case "webextension-permission-prompt": {
|
||||
const { info } = aSubject.wrappedJSObject;
|
||||
const { addon, install } = info;
|
||||
this.permissionPrompt(install, addon, info);
|
||||
this.permissionPromptRequest(install, addon, info);
|
||||
break;
|
||||
}
|
||||
case "webextension-optional-permission-prompt": {
|
||||
@ -960,9 +975,9 @@ export var GeckoViewWebExtension = {
|
||||
|
||||
async setPrivateBrowsingAllowed(aId, aAllowed) {
|
||||
if (aAllowed) {
|
||||
await lazy.ExtensionPermissions.add(aId, PRIVATE_BROWSING_PERMISSION);
|
||||
await lazy.ExtensionPermissions.add(aId, PRIVATE_BROWSING_PERMS);
|
||||
} else {
|
||||
await lazy.ExtensionPermissions.remove(aId, PRIVATE_BROWSING_PERMISSION);
|
||||
await lazy.ExtensionPermissions.remove(aId, PRIVATE_BROWSING_PERMS);
|
||||
}
|
||||
|
||||
// Reload the extension if it is already enabled. This ensures any change
|
||||
|
Loading…
x
Reference in New Issue
Block a user