Skip to content

Commit e83684f

Browse files
committed
SERP Settings Sync: Implement subscription event data channel in BrowserTabFragment and ViewModel
We create a new channel so subscriptionEventData events are not lost
1 parent 9a21717 commit e83684f

File tree

3 files changed

+133
-4
lines changed

3 files changed

+133
-4
lines changed

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

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import androidx.lifecycle.Observer
4343
import androidx.room.Room
4444
import androidx.test.filters.SdkSuppress
4545
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
46+
import app.cash.turbine.test
4647
import com.duckduckgo.adclick.api.AdClickManager
4748
import com.duckduckgo.app.ValueCaptorObserver
4849
import com.duckduckgo.app.accessibility.data.AccessibilitySettingsDataStore
@@ -233,6 +234,7 @@ import com.duckduckgo.common.utils.baseHost
233234
import com.duckduckgo.common.utils.device.DeviceInfo
234235
import com.duckduckgo.common.utils.plugins.PluginPoint
235236
import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider
237+
import com.duckduckgo.contentscopescripts.api.ContentScopeScriptsSubscriptionEventPlugin
236238
import com.duckduckgo.downloads.api.DownloadStateListener
237239
import com.duckduckgo.downloads.api.FileDownloader
238240
import com.duckduckgo.downloads.api.FileDownloader.PendingFileDownload
@@ -259,12 +261,8 @@ import com.duckduckgo.feature.toggles.api.Toggle
259261
import com.duckduckgo.feature.toggles.api.Toggle.State
260262
import com.duckduckgo.history.api.HistoryEntry.VisitedPage
261263
import com.duckduckgo.history.api.NavigationHistory
262-
import com.duckduckgo.js.messaging.api.AddDocumentStartJavaScriptPlugin
263264
import com.duckduckgo.js.messaging.api.JsCallbackData
264-
import com.duckduckgo.js.messaging.api.PostMessageWrapperPlugin
265265
import com.duckduckgo.js.messaging.api.SubscriptionEventData
266-
import com.duckduckgo.js.messaging.api.WebMessagingPlugin
267-
import com.duckduckgo.js.messaging.api.WebViewCompatMessageCallback
268266
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed
269267
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.MALWARE
270268
import com.duckduckgo.newtabpage.impl.pixels.NewTabPixels
@@ -290,6 +288,7 @@ import com.duckduckgo.savedsites.api.models.SavedSite.Favorite
290288
import com.duckduckgo.savedsites.impl.SavedSitesPixelName
291289
import com.duckduckgo.serp.logos.api.SerpEasterEggLogosToggles
292290
import com.duckduckgo.serp.logos.api.SerpLogo
291+
import com.duckduckgo.settings.api.SettingsPageFeature
293292
import com.duckduckgo.site.permissions.api.SitePermissionsManager
294293
import com.duckduckgo.site.permissions.api.SitePermissionsManager.LocationPermissionRequest
295294
import com.duckduckgo.site.permissions.api.SitePermissionsManager.SitePermissionQueryResponse
@@ -619,6 +618,9 @@ class BrowserTabViewModelTest {
619618

620619
private val mockDeviceAppLookup: DeviceAppLookup = mock()
621620

621+
private lateinit var fakeContentScopeScriptsSubscriptionEventPluginPoint: FakeContentScopeScriptsSubscriptionEventPluginPoint
622+
private var fakeSettingsPageFeature = FakeFeatureToggleFactory.create(SettingsPageFeature::class.java)
623+
622624
@Before
623625
fun before() =
624626
runTest {
@@ -849,6 +851,8 @@ class BrowserTabViewModelTest {
849851
addressBarTrackersAnimationFeatureToggle = mockAddressBarTrackersAnimationFeatureToggle,
850852
autoconsentPixelManager = mockAutoconsentPixelManager,
851853
omnibarFeatureRepository = mockOmnibarFeatureRepository,
854+
contentScopeScriptsSubscriptionEventPluginPoint = fakeContentScopeScriptsSubscriptionEventPluginPoint,
855+
settingsPageFeature = fakeSettingsPageFeature,
852856
)
853857

854858
testee.loadData("abc", null, false, false)
@@ -7944,6 +7948,104 @@ class BrowserTabViewModelTest {
79447948
override fun getPlugins(): Collection<PostMessageWrapperPlugin> = listOf(plugin)
79457949
}
79467950

7951+
class FakeContentScopeScriptsSubscriptionEventPlugin(
7952+
private val eventData: SubscriptionEventData,
7953+
) : ContentScopeScriptsSubscriptionEventPlugin {
7954+
override fun getSubscriptionEventData(): SubscriptionEventData = eventData
7955+
}
7956+
7957+
class FakeContentScopeScriptsSubscriptionEventPluginPoint() : PluginPoint<ContentScopeScriptsSubscriptionEventPlugin> {
7958+
7959+
private val plugins: MutableList<ContentScopeScriptsSubscriptionEventPlugin> = mutableListOf()
7960+
7961+
fun addPlugins(plugins: List<ContentScopeScriptsSubscriptionEventPlugin>) {
7962+
this.plugins.addAll(plugins)
7963+
}
7964+
7965+
override fun getPlugins(): Collection<ContentScopeScriptsSubscriptionEventPlugin> = plugins
7966+
}
7967+
7968+
@Test
7969+
fun whenOnViewResumedWithNoPluginsThenNoSubscriptionEventsSent() = runTest {
7970+
fakeSettingsPageFeature.serpSettingsSync().setRawStoredState(State(enable = true))
7971+
7972+
testee.onViewResumed()
7973+
7974+
testee.subscriptionEventDataFlow.test {
7975+
expectNoEvents()
7976+
cancelAndIgnoreRemainingEvents()
7977+
}
7978+
}
7979+
7980+
@Test
7981+
fun whenOnViewResumedWithPluginsThenSubscriptionEventsSent() = runTest {
7982+
fakeSettingsPageFeature.serpSettingsSync().setRawStoredState(State(enable = true))
7983+
val events = mutableListOf<SubscriptionEventData>().apply {
7984+
add(
7985+
SubscriptionEventData(
7986+
featureName = "event1",
7987+
subscriptionName = "subscription1",
7988+
params = JSONObject().put("param1", "value1"),
7989+
),
7990+
)
7991+
add(
7992+
SubscriptionEventData(
7993+
featureName = "event2",
7994+
subscriptionName = "subscription2",
7995+
params = JSONObject().put("param2", "value2"),
7996+
),
7997+
)
7998+
}
7999+
8000+
fakeContentScopeScriptsSubscriptionEventPluginPoint.addPlugins(
8001+
events.map { FakeContentScopeScriptsSubscriptionEventPlugin(it) },
8002+
)
8003+
8004+
testee.onViewResumed()
8005+
8006+
testee.subscriptionEventDataFlow.test {
8007+
for (expectedEvent in events) {
8008+
val emittedEvent = awaitItem()
8009+
assertEquals(expectedEvent.featureName, emittedEvent.featureName)
8010+
assertEquals(expectedEvent.subscriptionName, emittedEvent.subscriptionName)
8011+
assertEquals(expectedEvent.params.toString(), emittedEvent.params.toString())
8012+
}
8013+
cancelAndIgnoreRemainingEvents()
8014+
}
8015+
}
8016+
8017+
@Test
8018+
fun whenOnViewResumedWithPluginsAndSerpSettingsFeatureFlagOffThenNoEventsSent() = runTest {
8019+
fakeSettingsPageFeature.serpSettingsSync().setRawStoredState(State(enable = false))
8020+
val events = mutableListOf<SubscriptionEventData>().apply {
8021+
add(
8022+
SubscriptionEventData(
8023+
featureName = "event1",
8024+
subscriptionName = "subscription1",
8025+
params = JSONObject().put("param1", "value1"),
8026+
),
8027+
)
8028+
add(
8029+
SubscriptionEventData(
8030+
featureName = "event2",
8031+
subscriptionName = "subscription2",
8032+
params = JSONObject().put("param2", "value2"),
8033+
),
8034+
)
8035+
}
8036+
8037+
fakeContentScopeScriptsSubscriptionEventPluginPoint.addPlugins(
8038+
events.map { FakeContentScopeScriptsSubscriptionEventPlugin(it) },
8039+
)
8040+
8041+
testee.onViewResumed()
8042+
8043+
testee.subscriptionEventDataFlow.test {
8044+
expectNoEvents()
8045+
cancelAndIgnoreRemainingEvents()
8046+
}
8047+
}
8048+
79478049
@Test
79488050
fun whenVpnMenuClickedWithNotSubscribedStateThenPixelFiredWithPillStatus() {
79498051
testee.browserViewState.value = browserViewState().copy(

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,15 @@ class BrowserTabFragment :
992992
pendingUploadTask = null
993993
}
994994
viewModel.handleExternalLaunch(isLaunchedFromExternalApp)
995+
996+
observeSubscriptionEventDataChannel()
997+
}
998+
999+
private fun observeSubscriptionEventDataChannel() {
1000+
viewModel.subscriptionEventDataFlow.onEach { subscriptionEventData ->
1001+
logcat { "SERP-Settings: Sending subscription event data to content scope scripts: $subscriptionEventData" }
1002+
contentScopeScripts.sendSubscriptionEvent(subscriptionEventData)
1003+
}.launchIn(lifecycleScope)
9951004
}
9961005

9971006
private fun resumeWebView() {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ import com.duckduckgo.common.utils.isMobileSite
308308
import com.duckduckgo.common.utils.plugins.PluginPoint
309309
import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider
310310
import com.duckduckgo.common.utils.toDesktopUri
311+
import com.duckduckgo.contentscopescripts.api.ContentScopeScriptsSubscriptionEventPlugin
311312
import com.duckduckgo.di.scopes.FragmentScope
312313
import com.duckduckgo.downloads.api.DownloadCommand
313314
import com.duckduckgo.downloads.api.DownloadStateListener
@@ -323,6 +324,7 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
323324
import com.duckduckgo.feature.toggles.api.Toggle
324325
import com.duckduckgo.history.api.NavigationHistory
325326
import com.duckduckgo.js.messaging.api.JsCallbackData
327+
import com.duckduckgo.js.messaging.api.SubscriptionEventData
326328
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed
327329
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.MALWARE
328330
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.PHISHING
@@ -350,6 +352,7 @@ import com.duckduckgo.savedsites.impl.dialogs.EditSavedSiteDialogFragment.Delete
350352
import com.duckduckgo.savedsites.impl.dialogs.EditSavedSiteDialogFragment.EditSavedSiteListener
351353
import com.duckduckgo.serp.logos.api.SerpEasterEggLogosToggles
352354
import com.duckduckgo.serp.logos.api.SerpLogo
355+
import com.duckduckgo.settings.api.SettingsPageFeature
353356
import com.duckduckgo.site.permissions.api.SitePermissionsManager
354357
import com.duckduckgo.site.permissions.api.SitePermissionsManager.LocationPermissionRequest
355358
import com.duckduckgo.site.permissions.api.SitePermissionsManager.SitePermissionQueryResponse
@@ -365,6 +368,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
365368
import kotlinx.coroutines.FlowPreview
366369
import kotlinx.coroutines.Job
367370
import kotlinx.coroutines.async
371+
import kotlinx.coroutines.channels.Channel
368372
import kotlinx.coroutines.delay
369373
import kotlinx.coroutines.flow.Flow
370374
import kotlinx.coroutines.flow.MutableStateFlow
@@ -383,6 +387,7 @@ import kotlinx.coroutines.flow.flowOn
383387
import kotlinx.coroutines.flow.launchIn
384388
import kotlinx.coroutines.flow.map
385389
import kotlinx.coroutines.flow.onEach
390+
import kotlinx.coroutines.flow.receiveAsFlow
386391
import kotlinx.coroutines.flow.stateIn
387392
import kotlinx.coroutines.launch
388393
import kotlinx.coroutines.withContext
@@ -490,6 +495,8 @@ class BrowserTabViewModel @Inject constructor(
490495
private val addressBarTrackersAnimationFeatureToggle: AddressBarTrackersAnimationFeatureToggle,
491496
private val autoconsentPixelManager: AutoconsentPixelManager,
492497
private val omnibarFeatureRepository: OmnibarFeatureRepository,
498+
private val contentScopeScriptsSubscriptionEventPluginPoint: PluginPoint<ContentScopeScriptsSubscriptionEventPlugin>,
499+
private val settingsPageFeature: SettingsPageFeature,
493500
) : ViewModel(),
494501
WebViewClientListener,
495502
EditSavedSiteListener,
@@ -539,6 +546,9 @@ class BrowserTabViewModel @Inject constructor(
539546

540547
private var activeExperiments: List<Toggle>? = null
541548

549+
private val _subscriptionEventDataChannel = Channel<SubscriptionEventData>(capacity = Channel.BUFFERED)
550+
val subscriptionEventDataFlow: Flow<SubscriptionEventData> = _subscriptionEventDataChannel.receiveAsFlow()
551+
542552
data class HiddenBookmarksIds(
543553
val favorites: List<String> = emptyList(),
544554
val bookmarks: List<String> = emptyList(),
@@ -941,6 +951,14 @@ class BrowserTabViewModel @Inject constructor(
941951
lastFullSiteUrlEnabled = settingsDataStore.isFullUrlEnabled
942952
command.value = Command.RefreshOmnibar
943953
}
954+
955+
if (settingsPageFeature.serpSettingsSync().isEnabled()) {
956+
viewModelScope.launch {
957+
contentScopeScriptsSubscriptionEventPluginPoint.getPlugins().forEach { plugin ->
958+
_subscriptionEventDataChannel.send(plugin.getSubscriptionEventData())
959+
}
960+
}
961+
}
944962
}
945963

946964
fun onViewVisible() {

0 commit comments

Comments
 (0)