Skip to content

Commit 5add8e8

Browse files
committed
Enable passkey support in browser for internal builds
1 parent a9a2db8 commit 5add8e8

File tree

5 files changed

+87
-0
lines changed

5 files changed

+87
-0
lines changed

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState
176176
import com.duckduckgo.app.browser.viewstate.OmnibarViewState
177177
import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState
178178
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
179+
import com.duckduckgo.app.browser.webauthn.WebViewPasskeyInitializer
179180
import com.duckduckgo.app.browser.webshare.WebShareChooser
180181
import com.duckduckgo.app.browser.webview.WebContentDebugging
181182
import com.duckduckgo.app.browser.webview.WebViewBlobDownloadFeature
@@ -579,6 +580,9 @@ class BrowserTabFragment :
579580
@Inject
580581
lateinit var browserAndInputScreenTransitionProvider: BrowserAndInputScreenTransitionProvider
581582

583+
@Inject
584+
lateinit var passkeyInitializer: WebViewPasskeyInitializer
585+
582586
/**
583587
* We use this to monitor whether the user was seeing the in-context Email Protection signup prompt
584588
* This is needed because the activity stack will be cleared if an external link is opened in our browser
@@ -3091,6 +3095,10 @@ class BrowserTabFragment :
30913095
}
30923096

30933097
WebView.setWebContentsDebuggingEnabled(webContentDebugging.isEnabled())
3098+
3099+
lifecycleScope.launch(dispatchers.main()) {
3100+
webView?.let { passkeyInitializer.configurePasskeySupport(it) }
3101+
}
30943102
}
30953103

30963104
private fun screenLock(data: JsCallbackData) {

app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoWebView.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,10 @@ class DuckDuckGoWebView : WebView, NestedScrollingChild3 {
412412
}
413413
}
414414

415+
fun isDestroyed(): Boolean {
416+
return isDestroyed
417+
}
418+
415419
@SuppressLint("RequiresFeature", "AddWebMessageListenerUsage")
416420
suspend fun safeAddWebMessageListener(
417421
webViewCapabilityChecker: WebViewCapabilityChecker,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.webauthn
18+
19+
import android.annotation.SuppressLint
20+
import androidx.webkit.WebSettingsCompat
21+
import androidx.webkit.WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_FOR_BROWSER
22+
import androidx.webkit.WebViewFeature
23+
import androidx.webkit.WebViewFeature.WEB_AUTHENTICATION
24+
import com.duckduckgo.app.browser.DuckDuckGoWebView
25+
import com.duckduckgo.autofill.api.AutofillFeature
26+
import com.duckduckgo.common.utils.DispatcherProvider
27+
import com.duckduckgo.di.scopes.AppScope
28+
import com.squareup.anvil.annotations.ContributesBinding
29+
import javax.inject.Inject
30+
import kotlinx.coroutines.withContext
31+
import logcat.logcat
32+
33+
interface WebViewPasskeyInitializer {
34+
suspend fun configurePasskeySupport(webView: DuckDuckGoWebView)
35+
}
36+
37+
@ContributesBinding(AppScope::class)
38+
class RealWebViewPasskeyInitializer @Inject constructor(
39+
private val autofillFeature: AutofillFeature,
40+
private val dispatchers: DispatcherProvider,
41+
) : WebViewPasskeyInitializer {
42+
43+
override suspend fun configurePasskeySupport(webView: DuckDuckGoWebView) {
44+
if (featureFlagEnabled() && webViewCapable()) {
45+
enablePasskeySupport(webView)
46+
}
47+
}
48+
49+
@SuppressLint("RequiresFeature")
50+
private suspend fun enablePasskeySupport(webView: DuckDuckGoWebView) {
51+
withContext(dispatchers.main()) {
52+
if (!webView.isDestroyed()) {
53+
WebSettingsCompat.setWebAuthenticationSupport(webView.settings, WEB_AUTHENTICATION_SUPPORT_FOR_BROWSER)
54+
logcat { "Autofill-passkey: WebView passkey support (WebAuthn) enabled with credential manager bridge" }
55+
}
56+
}
57+
}
58+
59+
private suspend fun featureFlagEnabled(): Boolean {
60+
return withContext(dispatchers.io()) {
61+
autofillFeature.passkeySupport().isEnabled()
62+
}
63+
}
64+
65+
private suspend fun webViewCapable(): Boolean {
66+
return withContext(dispatchers.main()) {
67+
WebViewFeature.isFeatureSupported(WEB_AUTHENTICATION)
68+
}
69+
}
70+
}

autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,7 @@ interface AutofillFeature {
160160

161161
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.TRUE)
162162
fun canShowImportOptionInAppSettings(): Toggle
163+
164+
@Toggle.DefaultValue(defaultValue = DefaultFeatureValue.INTERNAL)
165+
fun passkeySupport(): Toggle
163166
}

autofill/autofill-impl/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="com.duckduckgo.autofill.impl">
44

5+
<uses-permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN" />
6+
57
<application>
68
<activity
79
android:name=".ui.credential.management.importpassword.ImportPasswordsActivity"

0 commit comments

Comments
 (0)