2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-15 18:12:54 +00:00

Bug 1810044 1810045 - Delay reading clipboard and allow to paste non url

This commit is contained in:
Timshel 2023-09-28 17:44:31 +02:00 committed by mergify[bot]
parent 5a06549a3b
commit 06fb70e5db
2 changed files with 38 additions and 24 deletions

View File

@ -39,10 +39,7 @@ class ClipboardHandler(val context: Context) {
if (clipboard.isPrimaryClipEmpty()) { if (clipboard.isPrimaryClipEmpty()) {
return null return null
} }
if (clipboard.isPrimaryClipPlainText() || if (containsText()) {
clipboard.isPrimaryClipHtmlText() ||
clipboard.isPrimaryClipUrlText()
) {
return firstSafePrimaryClipItemText return firstSafePrimaryClipItemText
} }
return null return null
@ -101,6 +98,16 @@ class ClipboardHandler(val context: Context) {
} }
} }
/**
* Returns whether or not the clipboard data contains text.
* We cannot rely on `isPrimaryClipEmpty()` since it triggers a clipboard access system notification.
*/
fun containsText(): Boolean {
return clipboard.isPrimaryClipHtmlText() ||
clipboard.isPrimaryClipPlainText() ||
clipboard.isPrimaryClipUrlText()
}
@Suppress("MagicNumber") @Suppress("MagicNumber")
internal fun containsURL(): Boolean { internal fun containsURL(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@ -127,6 +134,13 @@ class ClipboardHandler(val context: Context) {
private fun ClipboardManager.isPrimaryClipUrlText() = private fun ClipboardManager.isPrimaryClipUrlText() =
primaryClipDescription?.hasMimeType(MIME_TYPE_TEXT_URL) ?: false primaryClipDescription?.hasMimeType(MIME_TYPE_TEXT_URL) ?: false
/**
* Returns whether or not the clipboard has any clip data.
* Reads the clip data, be aware this is a sensitive API as from Android 12 and above,
* accessing it will trigger a notification letting the user know the app has accessed the clipboard,
* make sure when you call this API that users are completely aware that we are accessing the clipboard.
* See https://github.com/mozilla-mobile/fenix/issues/22271 for more details.
*/
private fun ClipboardManager.isPrimaryClipEmpty() = primaryClip?.itemCount == 0 private fun ClipboardManager.isPrimaryClipEmpty() = primaryClip?.itemCount == 0
/** /**

View File

@ -26,6 +26,10 @@ import org.mozilla.fenix.databinding.BrowserToolbarPopupWindowBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
/**
* Since Android 12 reading the clipboard triggers an OS notification.
* As such it is important that we do not read it prematurely and only when the user trigger a paste action.
*/
object ToolbarPopupWindow { object ToolbarPopupWindow {
fun show( fun show(
view: WeakReference<View>, view: WeakReference<View>,
@ -35,12 +39,13 @@ object ToolbarPopupWindow {
copyVisible: Boolean = true, copyVisible: Boolean = true,
) { ) {
val context = view.get()?.context ?: return val context = view.get()?.context ?: return
val clipboard = context.components.clipboardHandler
val clipboardUrl = clipboard.getUrl()
val clipboardText = clipboard.text
if (!copyVisible && clipboardUrl == null) return
val isCustomTabSession = customTabId != null val isCustomTabSession = customTabId != null
val clipboard = context.components.clipboardHandler
val containsText = clipboard.containsText()
val containsUrl = clipboard.containsURL()
val pasteDeactivated = isCustomTabSession || (!containsText && !containsUrl)
if (!copyVisible && pasteDeactivated) return
val binding = BrowserToolbarPopupWindowBinding.inflate(LayoutInflater.from(context)) val binding = BrowserToolbarPopupWindowBinding.inflate(LayoutInflater.from(context))
val popupWindow = PopupWindow( val popupWindow = PopupWindow(
@ -57,9 +62,8 @@ object ToolbarPopupWindow {
popupWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) popupWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
binding.copy.isVisible = copyVisible binding.copy.isVisible = copyVisible
binding.paste.isVisible = containsText && !isCustomTabSession
binding.paste.isVisible = clipboardText != null && !isCustomTabSession binding.pasteAndGo.isVisible = containsUrl && !isCustomTabSession
binding.pasteAndGo.isVisible = clipboardUrl != null && !isCustomTabSession
if (copyVisible) { if (copyVisible) {
binding.copy.setOnClickListener { copyView -> binding.copy.setOnClickListener { copyView ->
@ -82,17 +86,21 @@ object ToolbarPopupWindow {
} }
} }
clipboardText?.let { text -> if (binding.paste.isVisible) {
binding.paste.setOnClickListener { binding.paste.setOnClickListener {
popupWindow.dismiss() popupWindow.dismiss()
handlePaste(text) handlePaste(clipboard.text.orEmpty())
} }
} }
clipboardUrl?.let { url -> if (binding.pasteAndGo.isVisible) {
binding.pasteAndGo.setOnClickListener { binding.pasteAndGo.setOnClickListener {
popupWindow.dismiss() popupWindow.dismiss()
handlePasteAndGo(url) clipboard.extractURL()?.also {
handlePasteAndGo(it)
} ?: run {
Logger("ToolbarPopupWindow").error("Clipboard contains URL but unable to read text")
}
} }
} }
@ -119,12 +127,4 @@ object ToolbarPopupWindow {
selectedTab?.readerState?.activeUrl ?: selectedTab?.content?.url selectedTab?.readerState?.activeUrl ?: selectedTab?.content?.url
} }
} }
private fun ClipboardHandler.getUrl(): String? {
if (containsURL()) {
text?.let { return it }
Logger("ToolbarPopupWindow").error("Clipboard contains URL but unable to read text")
}
return null
}
} }