Skip to content

Commit 60e1e39

Browse files
committed
Add delegate to simplify AddDocumentStartJavaScriptPlugin implementation
1 parent 19ff2d3 commit 60e1e39

File tree

3 files changed

+136
-53
lines changed

3 files changed

+136
-53
lines changed
Lines changed: 17 additions & 53 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,68 +16,32 @@
1616

1717
package com.duckduckgo.contentscopescripts.impl
1818

19-
import android.annotation.SuppressLint
20-
import android.webkit.WebView
21-
import androidx.webkit.ScriptHandler
22-
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker
23-
import com.duckduckgo.app.di.AppCoroutineScope
24-
import com.duckduckgo.browser.api.webviewcompat.WebViewCompatWrapper
25-
import com.duckduckgo.common.utils.DispatcherProvider
2619
import com.duckduckgo.contentscopescripts.api.contentscopeExperiments.ContentScopeExperiments
2720
import com.duckduckgo.di.scopes.FragmentScope
2821
import com.duckduckgo.js.messaging.api.AddDocumentStartJavaScriptPlugin
22+
import com.duckduckgo.js.messaging.api.AddDocumentStartJavaScriptScriptStrategy
23+
import com.duckduckgo.js.messaging.api.AddDocumentStartScriptDelegate
2924
import com.squareup.anvil.annotations.ContributesMultibinding
3025
import dagger.SingleInstanceIn
3126
import javax.inject.Inject
32-
import kotlinx.coroutines.CoroutineScope
33-
import kotlinx.coroutines.launch
34-
import kotlinx.coroutines.withContext
3527

3628
@SingleInstanceIn(FragmentScope::class)
3729
@ContributesMultibinding(FragmentScope::class)
3830
class ContentScopeScriptsAddDocumentStartJavaScriptPlugin @Inject constructor(
39-
private val webViewCompatContentScopeScripts: WebViewCompatContentScopeScripts,
40-
private val dispatcherProvider: DispatcherProvider,
41-
private val webViewCapabilityChecker: WebViewCapabilityChecker,
42-
private val webViewCompatWrapper: WebViewCompatWrapper,
43-
private val contentScopeExperiments: ContentScopeExperiments,
44-
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
45-
) : AddDocumentStartJavaScriptPlugin {
46-
private var script: ScriptHandler? = null
47-
private var currentScriptString: String? = null
48-
49-
@SuppressLint("RequiresFeature")
50-
override fun addDocumentStartJavaScript(
51-
webView: WebView,
52-
) {
53-
appCoroutineScope.launch {
54-
if (!webViewCompatContentScopeScripts.isEnabled() || !webViewCapabilityChecker.isSupported(
55-
WebViewCapabilityChecker.WebViewCapability.DocumentStartJavaScript,
56-
)
57-
) {
58-
return@launch
59-
}
31+
webViewCompatContentScopeScripts: WebViewCompatContentScopeScripts,
32+
contentScopeExperiments: ContentScopeExperiments,
33+
scriptInjectorDelegate: AddDocumentStartScriptDelegate,
34+
) : AddDocumentStartJavaScriptPlugin by scriptInjectorDelegate.createPlugin(
35+
object : AddDocumentStartJavaScriptScriptStrategy {
36+
override suspend fun canInject(): Boolean {
37+
return webViewCompatContentScopeScripts.isEnabled()
38+
}
6039

40+
override suspend fun getScriptString(): String {
6141
val activeExperiments = contentScopeExperiments.getActiveExperiments()
62-
val scriptString = webViewCompatContentScopeScripts.getScript(activeExperiments)
63-
if (scriptString == currentScriptString) {
64-
return@launch
65-
}
66-
script?.let {
67-
withContext(dispatcherProvider.main()) {
68-
it.remove()
69-
}
70-
script = null
71-
}
72-
73-
webViewCompatWrapper.addDocumentStartJavaScript(
74-
webView,
75-
scriptString,
76-
setOf("*"),
77-
)?.let {
78-
script = it
79-
currentScriptString = scriptString
80-
}
42+
return webViewCompatContentScopeScripts.getScript(activeExperiments)
8143
}
82-
}
83-
}
44+
45+
override val allowedOriginRules: Set<String> = setOf("*")
46+
},
47+
)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.js.messaging.impl
18+
19+
import android.annotation.SuppressLint
20+
import android.webkit.WebView
21+
import androidx.webkit.ScriptHandler
22+
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker
23+
import com.duckduckgo.app.di.AppCoroutineScope
24+
import com.duckduckgo.browser.api.webviewcompat.WebViewCompatWrapper
25+
import com.duckduckgo.common.utils.DispatcherProvider
26+
import com.duckduckgo.di.scopes.FragmentScope
27+
import com.duckduckgo.js.messaging.api.AddDocumentStartJavaScriptPlugin
28+
import com.duckduckgo.js.messaging.api.AddDocumentStartJavaScriptScriptStrategy
29+
import com.duckduckgo.js.messaging.api.AddDocumentStartScriptDelegate
30+
import com.squareup.anvil.annotations.ContributesBinding
31+
import javax.inject.Inject
32+
import kotlinx.coroutines.CoroutineScope
33+
import kotlinx.coroutines.launch
34+
import kotlinx.coroutines.withContext
35+
36+
@ContributesBinding(FragmentScope::class)
37+
/**
38+
* Delegate class that implements AddDocumentStartJavaScriptPlugin and handles
39+
* the common script injection logic
40+
*/
41+
class RealAddDocumentStartScriptDelegate @Inject constructor(
42+
private val webViewCapabilityChecker: WebViewCapabilityChecker,
43+
@AppCoroutineScope private val coroutineScope: CoroutineScope,
44+
private val dispatcherProvider: DispatcherProvider,
45+
private val webViewCompatWrapper: WebViewCompatWrapper,
46+
) : AddDocumentStartScriptDelegate {
47+
48+
private var script: ScriptHandler? = null
49+
private var currentScriptString: String? = null
50+
51+
override fun createPlugin(strategy: AddDocumentStartJavaScriptScriptStrategy): AddDocumentStartJavaScriptPlugin {
52+
return object : AddDocumentStartJavaScriptPlugin {
53+
@SuppressLint("RequiresFeature")
54+
override fun addDocumentStartJavaScript(webView: WebView) {
55+
coroutineScope.launch {
56+
if (!strategy.canInject() || !webViewCapabilityChecker.isSupported(
57+
WebViewCapabilityChecker.WebViewCapability.DocumentStartJavaScript,
58+
)
59+
) {
60+
return@launch
61+
}
62+
63+
val scriptString = strategy.getScriptString()
64+
if (scriptString == currentScriptString) {
65+
return@launch
66+
}
67+
script?.let {
68+
withContext(dispatcherProvider.main()) {
69+
it.remove()
70+
}
71+
script = null
72+
}
73+
74+
webViewCompatWrapper.addDocumentStartJavaScript(
75+
webView,
76+
scriptString,
77+
strategy.allowedOriginRules,
78+
)?.let {
79+
script = it
80+
currentScriptString = scriptString
81+
}
82+
}
83+
}
84+
}
85+
}
86+
}

js-messaging/js-messaging-api/src/main/java/com/duckduckgo/js/messaging/api/AddDocumentStartJavaScriptPlugin.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,36 @@ interface AddDocumentStartJavaScriptPlugin {
2929
webView: WebView,
3030
)
3131
}
32+
33+
/**
34+
* Strategy interface for script injection logic.
35+
* Allows different implementations to provide their own injection behavior.
36+
*/
37+
interface AddDocumentStartJavaScriptScriptStrategy {
38+
/**
39+
* Determines whether script injection should proceed (i.e. by checking feature flags).
40+
* @return true if injection is allowed, false otherwise
41+
*/
42+
suspend fun canInject(): Boolean
43+
44+
/**
45+
* Provides the script string to be injected.
46+
* @return the JavaScript code to inject
47+
*/
48+
suspend fun getScriptString(): String
49+
50+
/**
51+
* Defines the allowed origin rules for script injection.
52+
* @return set of allowed origin patterns
53+
*/
54+
val allowedOriginRules: Set<String>
55+
}
56+
57+
interface AddDocumentStartScriptDelegate {
58+
/**
59+
* Creates an AddDocumentStartJavaScriptPlugin implementation with the given [AddDocumentStartJavaScriptScriptStrategy].
60+
* @param strategy the strategy to use for determining injection behavior
61+
* @return [AddDocumentStartJavaScriptPlugin] implementation
62+
*/
63+
fun createPlugin(strategy: AddDocumentStartJavaScriptScriptStrategy): AddDocumentStartJavaScriptPlugin
64+
}

0 commit comments

Comments
 (0)