Skip to content

Commit 93c76cf

Browse files
committed
Add delegate to simplify AddDocumentStartJavaScriptPlugin implementation
1 parent 7732532 commit 93c76cf

File tree

3 files changed

+140
-49
lines changed

3 files changed

+140
-49
lines changed
Lines changed: 18 additions & 49 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,66 +16,35 @@
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.browser.api.WebViewCapabilityChecker.WebViewCapability.DocumentStartJavaScript
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
31-
import kotlinx.coroutines.withContext
3226
import javax.inject.Inject
3327

3428
@SingleInstanceIn(FragmentScope::class)
3529
@ContributesMultibinding(FragmentScope::class)
3630
class ContentScopeScriptsAddDocumentStartJavaScriptPlugin @Inject constructor(
37-
private val webViewCompatContentScopeScripts: WebViewCompatContentScopeScripts,
38-
private val dispatcherProvider: DispatcherProvider,
39-
private val webViewCapabilityChecker: WebViewCapabilityChecker,
40-
private val webViewCompatWrapper: WebViewCompatWrapper,
41-
private val contentScopeExperiments: ContentScopeExperiments,
42-
) : AddDocumentStartJavaScriptPlugin {
43-
private var script: ScriptHandler? = null
44-
private var currentScriptString: String? = null
45-
46-
@SuppressLint("RequiresFeature")
47-
override suspend fun addDocumentStartJavaScript(webView: WebView) {
48-
if (!webViewCompatContentScopeScripts.isEnabled() ||
49-
!webViewCapabilityChecker.isSupported(
50-
DocumentStartJavaScript,
51-
)
52-
) {
53-
return
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()
5438
}
5539

56-
val activeExperiments = contentScopeExperiments.getActiveExperiments()
57-
val scriptString = webViewCompatContentScopeScripts.getScript(activeExperiments)
58-
if (scriptString == currentScriptString) {
59-
return
60-
}
61-
script?.let {
62-
withContext(dispatcherProvider.main()) {
63-
it.remove()
64-
}
65-
script = null
40+
override suspend fun getScriptString(): String {
41+
val activeExperiments = contentScopeExperiments.getActiveExperiments()
42+
return webViewCompatContentScopeScripts.getScript(activeExperiments)
6643
}
6744

68-
webViewCompatWrapper
69-
.addDocumentStartJavaScript(
70-
webView,
71-
scriptString,
72-
setOf("*"),
73-
)?.let {
74-
script = it
75-
currentScriptString = scriptString
76-
}
77-
}
45+
override val allowedOriginRules: Set<String> = setOf("*")
7846

79-
override val context: String
80-
get() = "contentScopeScripts"
81-
}
47+
override val context: String
48+
get() = "contentScopeScripts"
49+
},
50+
)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 suspend fun addDocumentStartJavaScript(webView: WebView) {
55+
if (!strategy.canInject() || !webViewCapabilityChecker.isSupported(
56+
WebViewCapabilityChecker.WebViewCapability.DocumentStartJavaScript,
57+
)
58+
) {
59+
return
60+
}
61+
62+
val scriptString = strategy.getScriptString()
63+
if (scriptString == currentScriptString) {
64+
return
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+
strategy.allowedOriginRules,
77+
)?.let {
78+
script = it
79+
currentScriptString = scriptString
80+
}
81+
}
82+
83+
override val context: String
84+
get() = strategy.context
85+
}
86+
}
87+
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,38 @@ interface AddDocumentStartJavaScriptPlugin {
2828

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

0 commit comments

Comments
 (0)