Skip to content

Commit 1c56c20

Browse files
committed
Add support to addDocumentStartJavaScript
1 parent 4b32ad0 commit 1c56c20

File tree

10 files changed

+361
-17
lines changed

10 files changed

+361
-17
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserWebViewClientTest.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin
6161
import com.duckduckgo.app.browser.trafficquality.CustomHeaderAllowedChecker
6262
import com.duckduckgo.app.browser.trafficquality.remote.AndroidFeaturesHeaderProvider
6363
import com.duckduckgo.app.browser.uriloaded.UriLoadedManager
64-
import com.duckduckgo.app.global.model.Site
6564
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
6665
import com.duckduckgo.app.statistics.pixels.Pixel
6766
import com.duckduckgo.autoconsent.api.Autoconsent
@@ -1194,6 +1193,12 @@ class BrowserWebViewClientTest {
11941193
var countFinished = 0
11951194
var countStarted = 0
11961195

1196+
override fun onInit(
1197+
webView: WebView,
1198+
activeExperiments: List<Toggle>,
1199+
) {
1200+
}
1201+
11971202
override fun onPageStarted(
11981203
webView: WebView,
11991204
url: String?,
@@ -1203,7 +1208,11 @@ class BrowserWebViewClientTest {
12031208
countStarted++
12041209
}
12051210

1206-
override fun onPageFinished(webView: WebView, url: String?, site: Site?) {
1211+
override fun onPageFinished(
1212+
webView: WebView,
1213+
url: String?,
1214+
activeExperiments: List<Toggle>,
1215+
) {
12071216
countFinished++
12081217
}
12091218
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3006,6 +3006,7 @@ class BrowserTabFragment :
30063006
webView?.let {
30073007
it.isSafeWebViewEnabled = safeWebViewFeature.self().isEnabled()
30083008
it.webViewClient = webViewClient
3009+
webViewClient.triggerJSInit(it)
30093010
it.webChromeClient = webChromeClient
30103011
it.clearSslPreferences()
30113012

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,14 +463,29 @@ class BrowserWebViewClient @Inject constructor(
463463
webView.settings.mediaPlaybackRequiresUserGesture = mediaPlayback.doesMediaPlaybackRequireUserGestureForUrl(url)
464464
}
465465

466+
fun triggerJSInit(webView: WebView) {
467+
appCoroutineScope.launch(dispatcherProvider.main()) {
468+
val activeExperiments = contentScopeExperiments.getActiveExperiments()
469+
jsPlugins.getPlugins().forEach {
470+
it.onInit(webView, activeExperiments)
471+
}
472+
}
473+
}
474+
475+
// TODO check new API
466476
@UiThread
467477
override fun onPageFinished(webView: WebView, url: String?) {
468478
logcat(VERBOSE) { "onPageFinished webViewUrl: ${webView.url} URL: $url progress: ${webView.progress}" }
469479

470480
// See https://app.asana.com/0/0/1206159443951489/f (WebView limitations)
471481
if (webView.progress == 100) {
472482
jsPlugins.getPlugins().forEach {
473-
it.onPageFinished(webView, url, webViewClientListener?.getSite())
483+
val activeExperiments = webViewClientListener?.getSite()?.activeContentScopeExperiments ?: listOf()
484+
it.onPageFinished(
485+
webView,
486+
url,
487+
activeExperiments,
488+
)
474489
}
475490

476491
url?.let {

browser-api/src/main/java/com/duckduckgo/browser/api/JsInjectorPlugin.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,18 @@
1717
package com.duckduckgo.browser.api
1818

1919
import android.webkit.WebView
20-
import com.duckduckgo.app.global.model.Site
2120
import com.duckduckgo.feature.toggles.api.Toggle
2221

2322
/** Public interface to inject JS code to a website */
2423
interface JsInjectorPlugin {
24+
/**
25+
* On init of webview this is called and receives a [webView] instance.
26+
*/
27+
fun onInit(
28+
webView: WebView,
29+
activeExperiments: List<Toggle>,
30+
)
31+
2532
/**
2633
* This method is called during onPageStarted and receives a [webView] instance, the [url] of the website and the [site]
2734
*/
@@ -35,5 +42,9 @@ interface JsInjectorPlugin {
3542
/**
3643
* This method is called during onPageFinished and receives a [webView] instance, the [url] of the website and the [site]
3744
*/
38-
fun onPageFinished(webView: WebView, url: String?, site: Site?)
45+
fun onPageFinished(
46+
webView: WebView,
47+
url: String?,
48+
activeExperiments: List<Toggle>,
49+
)
3950
}

content-scope-scripts/content-scope-scripts-impl/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies {
3636
implementation project(':js-messaging-api')
3737
implementation project(':duckplayer-api')
3838
implementation project(':data-store-api')
39+
implementation AndroidX.webkit
3940

4041
anvil project(':anvil-compiler')
4142
implementation project(':anvil-annotations')

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,49 @@ import com.squareup.anvil.annotations.ContributesBinding
2121
import dagger.SingleInstanceIn
2222
import java.io.BufferedReader
2323
import javax.inject.Inject
24+
import javax.inject.Named
2425

2526
interface ContentScopeJSReader {
2627
fun getContentScopeJS(): String
2728
}
2829

29-
@SingleInstanceIn(AppScope::class)
30-
@ContributesBinding(AppScope::class)
31-
class RealContentScopeJSReader @Inject constructor() : ContentScopeJSReader {
30+
abstract class GenericContentScopeJSReader {
31+
abstract val fileName: String
32+
3233
private lateinit var contentScopeJS: String
3334

34-
override fun getContentScopeJS(): String {
35+
protected fun getContentScopeJSFile(): String {
3536
if (!this::contentScopeJS.isInitialized) {
36-
contentScopeJS = loadJs("contentScope.js")
37+
contentScopeJS = readResource(fileName).use { it?.readText() }.orEmpty()
3738
}
3839
return contentScopeJS
3940
}
4041

41-
fun loadJs(resourceName: String): String = readResource(resourceName).use { it?.readText() }.orEmpty()
42-
4342
private fun readResource(resourceName: String): BufferedReader? {
4443
return javaClass.classLoader?.getResource(resourceName)?.openStream()?.bufferedReader()
4544
}
4645
}
46+
47+
@SingleInstanceIn(AppScope::class)
48+
@ContributesBinding(AppScope::class, boundType = ContentScopeJSReader::class)
49+
@Named("contentScope")
50+
class RealContentScopeJSReader @Inject constructor() : GenericContentScopeJSReader(), ContentScopeJSReader {
51+
override val fileName: String
52+
get() = "contentScope.js"
53+
54+
override fun getContentScopeJS(): String {
55+
return getContentScopeJSFile()
56+
}
57+
}
58+
59+
@SingleInstanceIn(AppScope::class)
60+
@ContributesBinding(AppScope::class, boundType = ContentScopeJSReader::class)
61+
@Named("adsJS")
62+
class AdsContentScopeJSReader @Inject constructor() : GenericContentScopeJSReader(), ContentScopeJSReader {
63+
override val fileName: String
64+
get() = "adsjsContentScope.js"
65+
66+
override fun getContentScopeJS(): String {
67+
return getContentScopeJSFile()
68+
}
69+
}

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

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

1919
import android.webkit.WebView
20-
import com.duckduckgo.app.global.model.Site
20+
import androidx.webkit.ScriptHandler
21+
import androidx.webkit.WebViewCompat
22+
import androidx.webkit.WebViewFeature
2123
import com.duckduckgo.browser.api.JsInjectorPlugin
2224
import com.duckduckgo.di.scopes.AppScope
2325
import com.duckduckgo.feature.toggles.api.Toggle
@@ -27,7 +29,39 @@ import javax.inject.Inject
2729
@ContributesMultibinding(AppScope::class)
2830
class ContentScopeScriptsJsInjectorPlugin @Inject constructor(
2931
private val coreContentScopeScripts: CoreContentScopeScripts,
32+
private val adsJsContentScopeScripts: AdsJsContentScopeScripts,
3033
) : JsInjectorPlugin {
34+
private var script: ScriptHandler? = null
35+
private var currentScriptString: String? = null
36+
37+
private fun reloadJSIfNeeded(
38+
webView: WebView,
39+
activeExperiments: List<Toggle>,
40+
) {
41+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
42+
return
43+
}
44+
val scriptString = adsJsContentScopeScripts.getScript(activeExperiments)
45+
if (scriptString == currentScriptString) {
46+
return
47+
}
48+
script?.let {
49+
it.remove()
50+
script = null
51+
}
52+
if (coreContentScopeScripts.isEnabled()) {
53+
currentScriptString = scriptString
54+
script = WebViewCompat.addDocumentStartJavaScript(webView, scriptString, setOf("*"))
55+
}
56+
}
57+
58+
override fun onInit(
59+
webView: WebView,
60+
activeExperiments: List<Toggle>,
61+
) {
62+
reloadJSIfNeeded(webView, activeExperiments)
63+
}
64+
3165
override fun onPageStarted(
3266
webView: WebView,
3367
url: String?,
@@ -39,7 +73,11 @@ class ContentScopeScriptsJsInjectorPlugin @Inject constructor(
3973
}
4074
}
4175

42-
override fun onPageFinished(webView: WebView, url: String?, site: Site?) {
43-
// NOOP
76+
override fun onPageFinished(
77+
webView: WebView,
78+
url: String?,
79+
activeExperiments: List<Toggle>,
80+
) {
81+
reloadJSIfNeeded(webView, activeExperiments)
4482
}
4583
}

0 commit comments

Comments
 (0)