Skip to content

Commit

Permalink
Merge branch 'hotfix/5.216.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
CDRussell committed Oct 8, 2024
2 parents 43b85c9 + 9ca1df9 commit 8bf30a2
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class DuckDuckGoWebView : WebView, NestedScrollingChild3 {
private var nestedScrollHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this)
private val helper = CoordinatorLayoutHelper()

private var isDestroyed: Boolean = false
var isDestroyed: Boolean = false
var isSafeWebViewEnabled: Boolean = false

constructor(context: Context) : this(context, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability
import com.duckduckgo.browser.api.WebViewVersionProvider
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.common.utils.extensions.compareSemanticVersion
import com.duckduckgo.di.scopes.FragmentScope
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import javax.inject.Inject
import kotlinx.coroutines.withContext

@ContributesBinding(FragmentScope::class)
@ContributesBinding(AppScope::class)
class RealWebViewCapabilityChecker @Inject constructor(
private val dispatchers: DispatcherProvider,
private val webViewVersionProvider: WebViewVersionProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.browser

import android.annotation.SuppressLint
import android.webkit.WebView
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewCompat.WebMessageListener
import com.duckduckgo.app.browser.api.SafeWebMessageHandler
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import javax.inject.Inject
import timber.log.Timber

@SuppressLint("RequiresFeature", "AddWebMessageListenerUsage", "RemoveWebMessageListenerUsage")
@ContributesBinding(AppScope::class)
class SafeWebMessageHandlerImpl @Inject constructor(
private val webViewCapabilityChecker: WebViewCapabilityChecker,
) : SafeWebMessageHandler {

override suspend fun addWebMessageListener(
webView: WebView,
jsObjectName: String,
allowedOriginRules: Set<String>,
listener: WebMessageListener,
): Boolean = runCatching {
if (webViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener) && !isDestroyed(webView)) {
WebViewCompat.addWebMessageListener(webView, jsObjectName, allowedOriginRules, listener)
true
} else {
false
}
}.getOrElse { exception ->
Timber.e(exception, "Error adding WebMessageListener: $jsObjectName")
false
}

override suspend fun removeWebMessageListener(
webView: WebView,
jsObjectName: String,
): Boolean = runCatching {
if (webViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener) && !isDestroyed(webView)) {
WebViewCompat.removeWebMessageListener(webView, jsObjectName)
true
} else {
false
}
}.getOrElse { exception ->
Timber.e(exception, "Error removing WebMessageListener: $jsObjectName")
false
}

/**
* Can only check destroyed flag for DuckDuckGoWebView for now. If a normal WebView, assume not destroyed.
*/
private fun isDestroyed(webView: WebView): Boolean {
return if (webView is DuckDuckGoWebView) {
webView.isDestroyed
} else {
false
}
}
}
2 changes: 1 addition & 1 deletion app/version/version.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION=5.216.0
VERSION=5.216.1
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ interface AutofillFeature {
/**
* Kill switch for if we should inject Autofill javascript into the browser.
*
* @return `true` when the remote config has the global "canIntegrateAutofillInWebView" autofill sub-feature flag enabled
* @return `true` when the remote config has the global "canIntegrateWebMessageBasedAutofillInWebView" autofill sub-feature flag enabled
* If the remote feature is not present defaults to `true`
*/
@Toggle.DefaultValue(true)
fun canIntegrateAutofillInWebView(): Toggle
fun canIntegrateWebMessageBasedAutofillInWebView(): Toggle

/**
* @return `true` when the remote config has the global "canInjectCredentials" autofill sub-feature flag enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class AutofillGlobalCapabilityCheckerImpl @Inject constructor(
override suspend fun isAutofillEnabledByConfiguration(url: String): Boolean {
return withContext(dispatcherProvider.io()) {
val enabledAtTopLevel = isInternalTester() || isGlobalFeatureEnabled()
val canIntegrateAutofill = autofillFeature.canIntegrateAutofillInWebView().isEnabled()
val canIntegrateAutofill = autofillFeature.canIntegrateWebMessageBasedAutofillInWebView().isEnabled()
enabledAtTopLevel && canIntegrateAutofill && !isAnException(url)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package com.duckduckgo.autofill.impl

import android.annotation.SuppressLint
import android.webkit.WebView
import androidx.webkit.WebViewCompat
import com.duckduckgo.app.browser.api.SafeWebMessageHandler
import com.duckduckgo.autofill.api.AutofillFeature
import com.duckduckgo.autofill.api.BrowserAutofill
import com.duckduckgo.autofill.api.Callback
Expand Down Expand Up @@ -90,7 +90,7 @@ class InlineBrowserAutofill @Inject constructor(
}
}

private fun WebView.addWebMessageListener(
private suspend fun WebView.addWebMessageListener(
messageListener: AutofillWebMessageListener,
autofillCallback: Callback,
tabId: String,
Expand All @@ -106,22 +106,22 @@ class InlineBrowserAutofill @Inject constructor(
}

interface AutofillWebMessageAttacher {
fun addListener(
suspend fun addListener(
webView: WebView,
listener: AutofillWebMessageListener,
)
}

@SuppressLint("RequiresFeature")
@ContributesBinding(FragmentScope::class)
class AutofillWebMessageAttacherImpl @Inject constructor() : AutofillWebMessageAttacher {
class AutofillWebMessageAttacherImpl @Inject constructor(
private val safeWebMessageHandler: SafeWebMessageHandler,
) : AutofillWebMessageAttacher {

@SuppressLint("AddWebMessageListenerUsage")
// suppress AddWebMessageListenerUsage, we don't have access to DuckDuckGoWebView here.
override fun addListener(
override suspend fun addListener(
webView: WebView,
listener: AutofillWebMessageListener,
) {
WebViewCompat.addWebMessageListener(webView, listener.key, listener.origins, listener)
safeWebMessageHandler.addWebMessageListener(webView, listener.key, listener.origins, listener)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ class AutofillGlobalCapabilityCheckerImplGlobalFeatureTest(
private fun configureCanIntegrateAutofillSubfeature(isEnabled: Boolean) {
val toggle: Toggle = mock()
whenever(toggle.isEnabled()).thenReturn(isEnabled)
whenever(autofillFeature.canIntegrateAutofillInWebView()).thenReturn(toggle)
whenever(autofillFeature.canIntegrateWebMessageBasedAutofillInWebView()).thenReturn(toggle)
}

private fun configureIfUrlIsException(isException: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class InlineBrowserAutofillTest {
canSaveCredentials: Boolean = true,
canGeneratePassword: Boolean = true,
canAccessCredentialManagement: Boolean = true,
canIntegrateAutofillInWebView: Boolean = true,
canIntegrateWebMessageBasedAutofillInWebView: Boolean = true,
deviceWebViewSupportsAutofill: Boolean = true,
): InlineBrowserAutofill {
val autofillFeature = FakeFeatureToggleFactory.create(AutofillFeature::class.java)
Expand All @@ -104,10 +104,10 @@ class InlineBrowserAutofillTest {
autofillFeature.canSaveCredentials().setRawStoredState(State(enable = canSaveCredentials))
autofillFeature.canGeneratePasswords().setRawStoredState(State(enable = canGeneratePassword))
autofillFeature.canAccessCredentialManagement().setRawStoredState(State(enable = canAccessCredentialManagement))
autofillFeature.canIntegrateAutofillInWebView().setRawStoredState(State(enable = canIntegrateAutofillInWebView))
autofillFeature.canIntegrateWebMessageBasedAutofillInWebView().setRawStoredState(State(enable = canIntegrateWebMessageBasedAutofillInWebView))

whenever(capabilityChecker.webViewSupportsAutofill()).thenReturn(deviceWebViewSupportsAutofill)
whenever(capabilityChecker.canInjectCredentialsToWebView(any())).thenReturn(canIntegrateAutofillInWebView)
whenever(capabilityChecker.canInjectCredentialsToWebView(any())).thenReturn(canInjectCredentials)

return InlineBrowserAutofill(
autofillCapabilityChecker = capabilityChecker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() {
val autofillEnabled = autofillFeature.self()
val onByDefault = autofillFeature.onByDefault()
val onForExistingUsers = autofillFeature.onForExistingUsers()
val canIntegrateAutofill = autofillFeature.canIntegrateAutofillInWebView()
val canIntegrateAutofill = autofillFeature.canIntegrateWebMessageBasedAutofillInWebView()
val canSaveCredentials = autofillFeature.canSaveCredentials()
val canInjectCredentials = autofillFeature.canInjectCredentials()
val canGeneratePasswords = autofillFeature.canGeneratePasswords()
Expand Down
2 changes: 2 additions & 0 deletions browser-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies {

// LiveData
implementation AndroidX.lifecycle.liveDataKtx

implementation AndroidX.webkit
}
android {
namespace 'com.duckduckgo.browser.api'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.browser.api

import android.webkit.WebView
import androidx.webkit.WebViewCompat.WebMessageListener

/**
* Add and remove web message listeners to a WebView, guarded by extra checks to ensure WebView compatibility
*/
interface SafeWebMessageHandler {

suspend fun addWebMessageListener(
webView: WebView,
jsObjectName: String,
allowedOriginRules: Set<String>,
listener: WebMessageListener,
): Boolean

suspend fun removeWebMessageListener(
webView: WebView,
jsObjectName: String,
): Boolean
}

0 comments on commit 8bf30a2

Please sign in to comment.