Skip to content

Commit 1088b92

Browse files
authored
Add support for addDocumentStartJavaScript (#6600)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1205008441501016/task/1211044367033330?focus=true ### Description * Add support for injecting a subset of C-S-S features using `addDocumentStartJavaScript`, keeping support for `activeExperiments` compatible with both `addDocumentStartJavaScript` and `evaluateJavaScript` * C-S-S sends us a ping message so we have a reference to a reply proxy that we store and use to send messages after a user action is initiated on the native side * Process messages by context, then feature, and then method * Refactor message posting fallback mechanism * Make scoping/instancing explicit ### Steps to test this PR **Pre-requisites** - [x] Install C-S-S 11.15.0 - [x] Load privacy-config from https://duckduckgo.github.io/privacy-configuration/pr-3624/v4/android-config.json - [x] Set version to 5.300.1 in version.properties - [x] Add logs to `WebViewCompatBreakageContentScopeJsMessageHandler#process` and `WebViewCompatWebCompatMessagingPlugin#postMessage` - [x] Enable `useNewWebCompatApis` under feature flag inventory (enabled by default) _Desktop mode_ - [x] Load wikipedia.org - [x] Open menu - [x] Switch to desktop mode - [x] Check desktop mode is loaded _Breakage reporting_ - [x] Load a page - [x] Open menu - [x] Check subscription with name `getBreakageReportValues` is sent, and message with method `breakageReportResult` is received _Disable protections_ - [x] Load http://privacy-test-pages.site/privacy-protections/gpc/ - [x] Click start test - [x] Check: - [x] `top frame header - "1"` - [x] `top frame JS API - true` - [x] `frame JS API - true` - [x] Open menu - [x] Click Disable Privacy Protection - [x] Click start test - [x] Check: - [x] `top frame header - ...` - [x] `top frame JS API - ...` - [x] `frame JS API - ...` _Feature enabled_ - [x] Enable `useNewWebCompatApis` under feature flag inventory (enabled by default) - [x] Open https://w3c.github.io/web-share/demos/share-files.html - [x] Click "Share" - [x] Dismiss native share popup - [x] Check message displayed: "Error sharing: Abort Error: Share canceled" _Feature enabled_ - [x] Enable `useNewWebCompatApis` under feature flag inventory (enabled by default) - [x] Open http://privacy-test-pages.site/privacy-protections/gpc/ - [x] Click on "Start test" - [x] Check `frame JS API - true` _Feature disabled_ - [x] Disable `useNewWebCompatApis` under feature flag inventory (enabled by default) - [x] Open http://privacy-test-pages.site/privacy-protections/gpc/ - [x] Click on "Start test" - [x] Check `frame JS API - ...` _Feature enabled_ - [x] Enable `useNewWebCompatApis` under feature flag inventory (enabled by default) - [x] Open http://privacy-test-pages.site/privacy-protections/gpc/ - [x] Check logs for `DebugFlagGlobalHandler addDebugFlag: fingerprintingScreenSize` ### UI changes No UI changes
1 parent 637e057 commit 1088b92

File tree

50 files changed

+3009
-130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3009
-130
lines changed

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

Lines changed: 140 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.OpenDuckPlayerInNewTab.On
8484
import com.duckduckgo.duckplayer.api.DuckPlayer.OpenDuckPlayerInNewTab.Unavailable
8585
import com.duckduckgo.feature.toggles.api.Toggle
8686
import com.duckduckgo.history.api.NavigationHistory
87+
import com.duckduckgo.js.messaging.api.AddDocumentStartJavaScriptPlugin
88+
import com.duckduckgo.js.messaging.api.PostMessageWrapperPlugin
89+
import com.duckduckgo.js.messaging.api.SubscriptionEventData
90+
import com.duckduckgo.js.messaging.api.WebMessagingPlugin
91+
import com.duckduckgo.js.messaging.api.WebViewCompatMessageCallback
8792
import com.duckduckgo.privacy.config.api.AmpLinks
8893
import com.duckduckgo.subscriptions.api.Subscriptions
8994
import com.duckduckgo.user.agent.api.ClientBrandHintProvider
@@ -97,6 +102,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
97102
import kotlinx.coroutines.launch
98103
import kotlinx.coroutines.test.TestScope
99104
import kotlinx.coroutines.test.runTest
105+
import org.json.JSONObject
100106
import org.junit.Before
101107
import org.junit.Rule
102108
import org.junit.Test
@@ -112,6 +118,8 @@ import org.mockito.kotlin.verify
112118
import org.mockito.kotlin.verifyNoInteractions
113119
import org.mockito.kotlin.whenever
114120

121+
private val mockToggle: Toggle = mock()
122+
115123
class BrowserWebViewClientTest {
116124

117125
@get:Rule
@@ -158,6 +166,10 @@ class BrowserWebViewClientTest {
158166
private val openInNewTabFlow: MutableSharedFlow<OpenDuckPlayerInNewTab> = MutableSharedFlow()
159167
private val mockUriLoadedManager: UriLoadedManager = mock()
160168
private val mockAndroidBrowserConfigFeature: AndroidBrowserConfigFeature = mock()
169+
private val mockContentScopeExperiments: ContentScopeExperiments = mock()
170+
private val fakeAddDocumentStartJavaScriptPlugins = FakeAddDocumentStartJavaScriptPluginPoint()
171+
private val fakeMessagingPlugins = FakeWebMessagingPluginPoint()
172+
private val fakePostMessageWrapperPlugins = FakePostMessageWrapperPluginPoint()
161173
private val mockAndroidFeaturesHeaderPlugin = AndroidFeaturesHeaderPlugin(
162174
mockDuckDuckGoUrlDetector,
163175
mockCustomHeaderGracePeriodChecker,
@@ -166,15 +178,13 @@ class BrowserWebViewClientTest {
166178
mock(),
167179
)
168180
private val mockDuckChat: DuckChat = mock()
169-
private val mockContentScopeExperiments: ContentScopeExperiments = mock()
170181

171182
@UiThreadTest
172183
@Before
173184
fun setup() = runTest {
174185
webView = TestWebView(context)
175186
whenever(mockDuckPlayer.observeShouldOpenInNewTab()).thenReturn(openInNewTabFlow)
176-
val toggle: Toggle = mock()
177-
whenever(mockContentScopeExperiments.getActiveExperiments()).thenReturn(listOf(toggle))
187+
whenever(mockContentScopeExperiments.getActiveExperiments()).thenReturn(listOf(mockToggle))
178188
testee = BrowserWebViewClient(
179189
webViewHttpAuthStore,
180190
trustedCertificateStore,
@@ -208,6 +218,9 @@ class BrowserWebViewClientTest {
208218
mockAndroidFeaturesHeaderPlugin,
209219
mockDuckChat,
210220
mockContentScopeExperiments,
221+
fakeAddDocumentStartJavaScriptPlugins,
222+
fakeMessagingPlugins,
223+
fakePostMessageWrapperPlugins,
211224
)
212225
testee.webViewClientListener = listener
213226
whenever(webResourceRequest.url).thenReturn(Uri.EMPTY)
@@ -227,11 +240,8 @@ class BrowserWebViewClientTest {
227240
@UiThreadTest
228241
@Test
229242
fun whenOnPageStartedCalledThenListenerNotified() = runTest {
230-
val toggle: Toggle = mock()
231-
whenever(mockContentScopeExperiments.getActiveExperiments()).thenReturn(listOf(toggle))
232-
233243
testee.onPageStarted(webView, EXAMPLE_URL, null)
234-
verify(listener).pageStarted(any(), eq(listOf(toggle)))
244+
verify(listener).pageStarted(any(), eq(listOf(mockToggle)))
235245
}
236246

237247
@UiThreadTest
@@ -333,6 +343,55 @@ class BrowserWebViewClientTest {
333343
assertEquals(0, jsPlugins.plugin.countStarted)
334344
}
335345

346+
@UiThreadTest
347+
@Test
348+
fun whenOnPageStartedThenReturnActiveExperiments() {
349+
val captor = argumentCaptor<List<Toggle>>()
350+
testee.onPageStarted(webView, EXAMPLE_URL, null)
351+
verify(listener).pageStarted(any(), captor.capture())
352+
assertTrue(captor.firstValue.contains(mockToggle))
353+
}
354+
355+
@UiThreadTest
356+
@Test
357+
fun whenConfigureWebViewThenInjectJsCode() {
358+
assertEquals(0, fakeAddDocumentStartJavaScriptPlugins.plugin.countInitted)
359+
val mockCallback = mock<WebViewCompatMessageCallback>()
360+
testee.configureWebView(DuckDuckGoWebView(context), mockCallback)
361+
assertEquals(1, fakeAddDocumentStartJavaScriptPlugins.plugin.countInitted)
362+
}
363+
364+
@UiThreadTest
365+
@Test
366+
fun whenConfigureWebViewThenAddWebMessageListener() {
367+
assertFalse(fakeMessagingPlugins.plugin.registered)
368+
val mockCallback = mock<WebViewCompatMessageCallback>()
369+
testee.configureWebView(DuckDuckGoWebView(context), mockCallback)
370+
assertTrue(fakeMessagingPlugins.plugin.registered)
371+
}
372+
373+
@UiThreadTest
374+
@Test
375+
fun whenDestroyThenRemoveWebMessageListener() = runTest {
376+
val mockCallback = mock<WebViewCompatMessageCallback>()
377+
val webView = DuckDuckGoWebView(context)
378+
testee.configureWebView(webView, mockCallback)
379+
assertTrue(fakeMessagingPlugins.plugin.registered)
380+
testee.destroy(webView)
381+
assertFalse(fakeMessagingPlugins.plugin.registered)
382+
}
383+
384+
@Test
385+
fun whenPostMessageThenCallPostContentScopeMessage() = runTest {
386+
val data = SubscriptionEventData("feature", "method", JSONObject())
387+
388+
assertFalse(fakePostMessageWrapperPlugins.plugin.postMessageCalled)
389+
390+
testee.postContentScopeMessage(data)
391+
392+
assertTrue(fakePostMessageWrapperPlugins.plugin.postMessageCalled)
393+
}
394+
336395
@UiThreadTest
337396
@Test
338397
fun whenOnReceivedHttpAuthRequestThenListenerNotified() {
@@ -1203,7 +1262,11 @@ class BrowserWebViewClientTest {
12031262
countStarted++
12041263
}
12051264

1206-
override fun onPageFinished(webView: WebView, url: String?, site: Site?) {
1265+
override fun onPageFinished(
1266+
webView: WebView,
1267+
url: String?,
1268+
site: Site?,
1269+
) {
12071270
countFinished++
12081271
}
12091272
}
@@ -1266,4 +1329,73 @@ class BrowserWebViewClientTest {
12661329
companion object {
12671330
const val EXAMPLE_URL = "https://example.com"
12681331
}
1332+
1333+
class FakeAddDocumentStartJavaScriptPlugin : AddDocumentStartJavaScriptPlugin {
1334+
1335+
var countInitted = 0
1336+
private set
1337+
1338+
override fun addDocumentStartJavaScript(
1339+
webView: WebView,
1340+
) {
1341+
countInitted++
1342+
}
1343+
}
1344+
1345+
class FakeAddDocumentStartJavaScriptPluginPoint : PluginPoint<AddDocumentStartJavaScriptPlugin> {
1346+
1347+
val plugin = FakeAddDocumentStartJavaScriptPlugin()
1348+
1349+
override fun getPlugins() = listOf(plugin)
1350+
}
1351+
1352+
class FakeWebMessagingPlugin : WebMessagingPlugin {
1353+
var registered = false
1354+
private set
1355+
1356+
override fun unregister(webView: WebView) {
1357+
registered = false
1358+
}
1359+
1360+
override fun register(
1361+
jsMessageCallback: WebViewCompatMessageCallback,
1362+
webView: WebView,
1363+
) {
1364+
registered = true
1365+
}
1366+
1367+
override fun postMessage(subscriptionEventData: SubscriptionEventData) {
1368+
}
1369+
1370+
override val context: String
1371+
get() = "test"
1372+
}
1373+
1374+
class FakeWebMessagingPluginPoint : PluginPoint<WebMessagingPlugin> {
1375+
val plugin = FakeWebMessagingPlugin()
1376+
1377+
override fun getPlugins(): Collection<WebMessagingPlugin> {
1378+
return listOf(plugin)
1379+
}
1380+
}
1381+
1382+
class FakePostMessageWrapperPlugin : PostMessageWrapperPlugin {
1383+
var postMessageCalled = false
1384+
private set
1385+
1386+
override fun postMessage(message: SubscriptionEventData) {
1387+
postMessageCalled = true
1388+
}
1389+
1390+
override val context: String
1391+
get() = "contentScopeScripts"
1392+
}
1393+
1394+
class FakePostMessageWrapperPluginPoint : PluginPoint<PostMessageWrapperPlugin> {
1395+
val plugin = FakePostMessageWrapperPlugin()
1396+
1397+
override fun getPlugins(): Collection<PostMessageWrapperPlugin> {
1398+
return listOf(plugin)
1399+
}
1400+
}
12691401
}

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

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

1919
import androidx.test.annotation.UiThreadTest
20+
import androidx.test.ext.junit.runners.AndroidJUnit4
2021
import androidx.test.platform.app.InstrumentationRegistry
2122
import org.junit.Assert.assertFalse
23+
import org.junit.Before
2224
import org.junit.Test
25+
import org.junit.runner.RunWith
2326

27+
@RunWith(AndroidJUnit4::class)
2428
class DuckDuckGoWebViewTest {
2529

30+
private lateinit var testee: DuckDuckGoWebView
31+
32+
@Before
33+
@UiThreadTest
34+
fun setUp() {
35+
val context = InstrumentationRegistry.getInstrumentation().targetContext
36+
testee = DuckDuckGoWebView(context)
37+
}
38+
2639
@Test
2740
@UiThreadTest
2841
fun whenWebViewInitialisedThenSafeBrowsingDisabled() {
29-
val context = InstrumentationRegistry.getInstrumentation().targetContext
30-
val testee = DuckDuckGoWebView(context)
3142
assertFalse(testee.settings.safeBrowsingEnabled)
3243
}
3344
}

0 commit comments

Comments
 (0)