Skip to content

Commit e629a48

Browse files
committed
Also use existing DuckDuckGoWebView methods for messages
1 parent 2257a40 commit e629a48

File tree

9 files changed

+166
-82
lines changed

9 files changed

+166
-82
lines changed

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

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,6 @@ import com.duckduckgo.duckchat.api.inputscreen.InputScreenActivityResultCodes
296296
import com.duckduckgo.duckchat.api.inputscreen.InputScreenActivityResultParams
297297
import com.duckduckgo.duckplayer.api.DuckPlayer
298298
import com.duckduckgo.duckplayer.api.DuckPlayerSettingsNoParams
299-
import com.duckduckgo.js.messaging.api.AdsjsMessaging
300299
import com.duckduckgo.js.messaging.api.JsCallbackData
301300
import com.duckduckgo.js.messaging.api.JsMessageCallback
302301
import com.duckduckgo.js.messaging.api.JsMessaging
@@ -513,10 +512,6 @@ class BrowserTabFragment :
513512
@Named("DuckPlayer")
514513
lateinit var duckPlayerScripts: JsMessaging
515514

516-
@Inject
517-
@Named("AdsjsContentScopeScripts")
518-
lateinit var adsJsContentScopeScripts: AdsjsMessaging
519-
520515
@Inject
521516
lateinit var webContentDebugging: WebContentDebugging
522517

@@ -3027,22 +3022,18 @@ class BrowserTabFragment :
30273022
it.isSafeWebViewEnabled = safeWebViewFeature.self().isEnabled()
30283023
it.webViewClient = webViewClient
30293024
lifecycleScope.launch(dispatchers.main()) {
3030-
webViewClient.configureWebView(it)
3031-
adsJsContentScopeScripts.register(
3032-
it,
3033-
object : JsMessageCallback() {
3034-
override fun process(
3035-
featureName: String,
3036-
method: String,
3037-
id: String?,
3038-
data: JSONObject?,
3039-
) {
3040-
viewModel.processJsCallbackMessage(featureName, method, id, data, isActiveCustomTab()) {
3041-
it.url
3042-
}
3025+
webViewClient.configureWebView(it, object : JsMessageCallback() {
3026+
override fun process(
3027+
featureName: String,
3028+
method: String,
3029+
id: String?,
3030+
data: JSONObject?,
3031+
) {
3032+
viewModel.processJsCallbackMessage(featureName, method, id, data, isActiveCustomTab()) {
3033+
it.url
30433034
}
3044-
},
3045-
)
3035+
}
3036+
})
30463037
}
30473038

30483039
it.webChromeClient = webChromeClient
@@ -3877,7 +3868,7 @@ class BrowserTabFragment :
38773868
if (::webViewContainer.isInitialized) webViewContainer.removeAllViews()
38783869
appCoroutineScope.launch(dispatchers.main()) {
38793870
webView?.let {
3880-
adsJsContentScopeScripts.unregister(it)
3871+
webViewClient.destroy(it)
38813872
it.destroy()
38823873
}
38833874
webView = null

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import com.duckduckgo.common.utils.CurrentTimeProvider
7171
import com.duckduckgo.common.utils.DispatcherProvider
7272
import com.duckduckgo.common.utils.plugins.PluginPoint
7373
import com.duckduckgo.contentscopescripts.api.AddDocumentStartJavaScriptPlugin
74+
import com.duckduckgo.contentscopescripts.api.WebMessagingPlugin
7475
import com.duckduckgo.contentscopescripts.api.contentscopeExperiments.ContentScopeExperiments
7576
import com.duckduckgo.cookies.api.CookieManagerProvider
7677
import com.duckduckgo.duckchat.api.DuckChat
@@ -80,6 +81,7 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
8081
import com.duckduckgo.duckplayer.api.DuckPlayer.OpenDuckPlayerInNewTab.On
8182
import com.duckduckgo.duckplayer.impl.DUCK_PLAYER_OPEN_IN_YOUTUBE_PATH
8283
import com.duckduckgo.history.api.NavigationHistory
84+
import com.duckduckgo.js.messaging.api.JsMessageCallback
8385
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed
8486
import com.duckduckgo.privacy.config.api.AmpLinks
8587
import com.duckduckgo.subscriptions.api.Subscriptions
@@ -91,6 +93,7 @@ import logcat.LogPriority.INFO
9193
import logcat.LogPriority.VERBOSE
9294
import logcat.LogPriority.WARN
9395
import logcat.logcat
96+
import org.json.JSONObject
9497

9598
private const val ABOUT_BLANK = "about:blank"
9699

@@ -128,6 +131,7 @@ class BrowserWebViewClient @Inject constructor(
128131
private val duckChat: DuckChat,
129132
private val contentScopeExperiments: ContentScopeExperiments,
130133
private val addDocumentStartJavascriptPlugins: PluginPoint<AddDocumentStartJavaScriptPlugin>,
134+
private val webMessagingPlugins: PluginPoint<WebMessagingPlugin>,
131135
) : WebViewClient() {
132136

133137
var webViewClientListener: WebViewClientListener? = null
@@ -465,14 +469,25 @@ class BrowserWebViewClient @Inject constructor(
465469
webView.settings.mediaPlaybackRequiresUserGesture = mediaPlayback.doesMediaPlaybackRequireUserGestureForUrl(url)
466470
}
467471

468-
fun configureWebView(webView: DuckDuckGoWebView) {
472+
fun configureWebView(webView: DuckDuckGoWebView, callback: JsMessageCallback) {
469473
appCoroutineScope.launch {
470474
val activeExperiments = contentScopeExperiments.getActiveExperiments()
471475
addDocumentStartJavascriptPlugins.getPlugins().forEach { plugin ->
472476
plugin.configureAddDocumentStartJavaScript(activeExperiments) { scriptString, allowedOrigins ->
473477
webView.safeAddDocumentStartJavaScript(scriptString, allowedOrigins)
474478
}
475479
}
480+
481+
482+
webMessagingPlugins.getPlugins().forEach { plugin ->
483+
plugin.register(webView, callback) { objectName, allowedOriginRules, webMessageListener ->
484+
webView.safeAddWebMessageListener(
485+
objectName,
486+
allowedOriginRules,
487+
webMessageListener,
488+
)
489+
}
490+
}
476491
}
477492
}
478493

@@ -760,6 +775,15 @@ class BrowserWebViewClient @Inject constructor(
760775
fun addExemptedMaliciousSite(url: Uri, feed: Feed) {
761776
requestInterceptor.addExemptedMaliciousSite(url, feed)
762777
}
778+
779+
suspend fun destroy(webView: DuckDuckGoWebView) {
780+
781+
webMessagingPlugins.getPlugins().forEach { plugin ->
782+
plugin.unregister { objectName ->
783+
webView.safeRemoveWebMessageListener(objectName)
784+
}
785+
}
786+
}
763787
}
764788

765789
enum class WebViewPixelName(override val pixelName: String) : Pixel.PixelName {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2023 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.plugins
18+
19+
import com.duckduckgo.anvil.annotations.ContributesPluginPoint
20+
import com.duckduckgo.contentscopescripts.api.AddDocumentStartJavaScriptPlugin
21+
import com.duckduckgo.contentscopescripts.api.WebMessagingPlugin
22+
import com.duckduckgo.di.scopes.AppScope
23+
24+
@ContributesPluginPoint(
25+
scope = AppScope::class,
26+
boundType = WebMessagingPlugin::class,
27+
)
28+
@Suppress("unused")
29+
interface UnusedWebMessagingPluginPoint
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.contentscopescripts.api
18+
19+
import android.webkit.WebView
20+
import androidx.webkit.ScriptHandler
21+
import androidx.webkit.WebViewCompat.WebMessageListener
22+
import com.duckduckgo.feature.toggles.api.Toggle
23+
import com.duckduckgo.js.messaging.api.JsMessageCallback
24+
import kotlinx.coroutines.withContext
25+
26+
interface WebMessagingPlugin {
27+
suspend fun register(
28+
webView: WebView,
29+
jsMessageCallback: JsMessageCallback?,
30+
registerer: suspend (objectName: String, allowedOriginRules: Set<String>, webMessageListener: WebMessageListener) -> Boolean)
31+
32+
suspend fun unregister(
33+
unregisterer: suspend (objectName: String) -> Boolean)
34+
}

content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/RealWebViewCompatWrapper.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.duckduckgo.contentscopescripts.impl
1818

1919
import android.annotation.SuppressLint
20+
import android.webkit.WebView
2021
import androidx.webkit.ScriptHandler
2122
import androidx.webkit.WebViewCompat
2223
import com.duckduckgo.di.scopes.AppScope
@@ -38,4 +39,20 @@ class RealWebViewCompatWrapper @Inject constructor() : WebViewCompatWrapper {
3839
): ScriptHandler {
3940
return WebViewCompat.addDocumentStartJavaScript(webView, script, allowedOriginRules)
4041
}
42+
43+
override fun removeWebMessageListener(webView: WebView, jsObjectName: String) {
44+
WebViewCompat.removeWebMessageListener(
45+
webView,
46+
jsObjectName,
47+
)
48+
}
49+
50+
override fun addWebMessageListener(
51+
webView: WebView,
52+
jsObjectName: String,
53+
allowedOriginRules: Set<String>,
54+
listener: WebViewCompat.WebMessageListener,
55+
) {
56+
return WebViewCompat.addWebMessageListener(webView, jsObjectName, allowedOriginRules, listener)
57+
}
4158
}

content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/WebViewCompatWrapper.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.duckduckgo.contentscopescripts.impl
1818

1919
import android.webkit.WebView
2020
import androidx.webkit.ScriptHandler
21+
import androidx.webkit.WebViewCompat.WebMessageListener
2122

2223
interface WebViewCompatWrapper {
2324

@@ -26,4 +27,16 @@ interface WebViewCompatWrapper {
2627
script: String,
2728
allowedOriginRules: Set<String>,
2829
): ScriptHandler
30+
31+
fun removeWebMessageListener(
32+
webView: WebView,
33+
jsObjectName: String,
34+
)
35+
36+
fun addWebMessageListener(
37+
webView: WebView,
38+
jsObjectName: String,
39+
allowedOriginRules: Set<String>,
40+
listener: WebMessageListener,
41+
)
2942
}
Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 DuckDuckGo
2+
* Copyright (c) 2025 DuckDuckGo
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,45 +16,41 @@
1616

1717
package com.duckduckgo.contentscopescripts.impl.messaging
1818

19-
import android.annotation.SuppressLint
2019
import android.webkit.WebView
21-
import androidx.webkit.WebViewCompat
22-
import androidx.webkit.WebViewFeature
20+
import androidx.webkit.WebViewCompat.WebMessageListener
2321
import com.duckduckgo.common.utils.DispatcherProvider
2422
import com.duckduckgo.common.utils.plugins.PluginPoint
2523
import com.duckduckgo.contentscopescripts.api.AdsjsContentScopeJsMessageHandlersPlugin
2624
import com.duckduckgo.contentscopescripts.api.GlobalContentScopeJsMessageHandlersPlugin
25+
import com.duckduckgo.contentscopescripts.api.WebMessagingPlugin
2726
import com.duckduckgo.contentscopescripts.impl.AdsJsContentScopeScripts
2827
import com.duckduckgo.di.scopes.ActivityScope
29-
import com.duckduckgo.js.messaging.api.AdsjsMessaging
3028
import com.duckduckgo.js.messaging.api.JsMessage
3129
import com.duckduckgo.js.messaging.api.JsMessageCallback
3230
import com.squareup.anvil.annotations.ContributesBinding
3331
import com.squareup.moshi.Moshi
34-
import javax.inject.Inject
35-
import javax.inject.Named
3632
import kotlinx.coroutines.withContext
37-
import logcat.LogPriority.ERROR
33+
import logcat.LogPriority
3834
import logcat.asLog
3935
import logcat.logcat
36+
import javax.inject.Inject
37+
import javax.inject.Named
4038

4139
private const val JS_OBJECT_NAME = "contentScopeAdsjs"
4240

4341
@ContributesBinding(ActivityScope::class)
4442
@Named("AdsjsContentScopeScripts")
45-
class AdsjsContentScopeMessaging @Inject constructor(
43+
class WebCompatMessagingPlugin @Inject constructor(
4644
private val handlers: PluginPoint<AdsjsContentScopeJsMessageHandlersPlugin>,
4745
private val globalHandlers: PluginPoint<GlobalContentScopeJsMessageHandlersPlugin>,
4846
private val adsJsContentScopeScripts: AdsJsContentScopeScripts,
4947
private val dispatcherProvider: DispatcherProvider,
50-
) : AdsjsMessaging {
48+
) : WebMessagingPlugin {
5149

5250
private val moshi = Moshi.Builder().add(JSONObjectAdapter()).build()
5351

54-
private lateinit var webView: WebView
55-
56-
override val context: String = "contentScopeScripts"
57-
override val allowedDomains: Set<String> = setOf("*")
52+
private val context: String = "contentScopeScripts"
53+
private val allowedDomains: Set<String> = setOf("*")
5854

5955
private fun process(
6056
message: String,
@@ -81,46 +77,40 @@ class AdsjsContentScopeMessaging @Inject constructor(
8177
}
8278
}
8379
} catch (e: Exception) {
84-
logcat(ERROR) { "Exception is ${e.asLog()}" }
80+
logcat(LogPriority.ERROR) { "Exception is ${e.asLog()}" }
8581
}
8682
}
8783

88-
@SuppressLint("AddWebMessageListenerUsage")
89-
override suspend fun register(webView: WebView, jsMessageCallback: JsMessageCallback?) {
84+
85+
override suspend fun register(
86+
webView: WebView,
87+
jsMessageCallback: JsMessageCallback?,
88+
registerer: suspend (objectName: String, allowedOriginRules: Set<String>, webMessageListener: WebMessageListener) -> Boolean)
89+
{
9090
if (withContext(dispatcherProvider.io()) { !adsJsContentScopeScripts.isEnabled() }) return
9191
if (jsMessageCallback == null) throw Exception("Callback cannot be null")
92-
this.webView = webView
9392

9493
runCatching {
95-
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
96-
WebViewCompat.addWebMessageListener(
97-
webView,
98-
JS_OBJECT_NAME,
99-
allowedDomains,
100-
) { _, message, _, _, replyProxy ->
101-
process(
102-
message.data ?: "",
103-
jsMessageCallback,
104-
)
105-
}
106-
true
107-
} else {
108-
false
109-
}
94+
95+
return@runCatching registerer(JS_OBJECT_NAME, allowedDomains,
96+
WebMessageListener { _, message, _, _, _ -> process(message.data ?: "", jsMessageCallback) }
97+
)
11098
}.getOrElse { exception ->
111-
logcat(ERROR) { "Error adding WebMessageListener for contentScopeAdsjs: ${exception.asLog()}" }
99+
logcat(LogPriority.ERROR) { "Error adding WebMessageListener for contentScopeAdsjs: ${exception.asLog()}" }
112100
false
113101
}
114102
}
115103

116-
override suspend fun unregister(webView: WebView) {
104+
override suspend fun unregister(
105+
unregisterer: suspend (objectName: String) -> Boolean
106+
) {
117107
if (!adsJsContentScopeScripts.isEnabled()) return
118108
withContext(dispatcherProvider.main()) {
119109
runCatching {
120-
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
121-
WebViewCompat.removeWebMessageListener(webView, JS_OBJECT_NAME)
122-
} else {
123-
logcat(ERROR) { "WebMessageListener is not supported on this WebView" }
110+
return@runCatching unregister(unregisterer)
111+
}.getOrElse { exception ->
112+
logcat(LogPriority.ERROR) {
113+
"Error removing WebMessageListener for contentScopeAdsjs: ${exception.asLog()}"
124114
}
125115
}
126116
}

0 commit comments

Comments
 (0)