Skip to content

Commit e58b586

Browse files
committed
WIP
1 parent d42adc7 commit e58b586

File tree

8 files changed

+165
-2
lines changed

8 files changed

+165
-2
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,14 @@ import com.google.android.material.snackbar.Snackbar
345345
import kotlinx.coroutines.CoroutineScope
346346
import kotlinx.coroutines.Job
347347
import kotlinx.coroutines.SupervisorJob
348+
import kotlinx.coroutines.channels.Channel
348349
import kotlinx.coroutines.delay
350+
import kotlinx.coroutines.flow.Flow
349351
import kotlinx.coroutines.flow.cancellable
350352
import kotlinx.coroutines.flow.collectLatest
351353
import kotlinx.coroutines.flow.launchIn
352354
import kotlinx.coroutines.flow.onEach
355+
import kotlinx.coroutines.flow.receiveAsFlow
353356
import kotlinx.coroutines.launch
354357
import kotlinx.coroutines.withContext
355358
import logcat.LogPriority.ERROR
@@ -983,6 +986,15 @@ class BrowserTabFragment :
983986
pendingUploadTask = null
984987
}
985988
viewModel.handleExternalLaunch(isLaunchedFromExternalApp)
989+
990+
observeSubscriptionEventDataChannel()
991+
}
992+
993+
private fun observeSubscriptionEventDataChannel() {
994+
viewModel.subscriptionEventFlow.onEach { subscriptionEventData ->
995+
logcat { "SERP-Settings: Sending subscription event data to content scope scripts: $subscriptionEventData" }
996+
contentScopeScripts.sendSubscriptionEvent(subscriptionEventData)
997+
}.launchIn(lifecycleScope)
986998
}
987999

9881000
private fun resumeWebView() {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ import com.duckduckgo.common.utils.isMobileSite
307307
import com.duckduckgo.common.utils.plugins.PluginPoint
308308
import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider
309309
import com.duckduckgo.common.utils.toDesktopUri
310+
import com.duckduckgo.contentscopescripts.api.ContentScopeScriptsSubscriptionEventPlugin
310311
import com.duckduckgo.di.scopes.FragmentScope
311312
import com.duckduckgo.downloads.api.DownloadCommand
312313
import com.duckduckgo.downloads.api.DownloadStateListener
@@ -322,6 +323,7 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
322323
import com.duckduckgo.feature.toggles.api.Toggle
323324
import com.duckduckgo.history.api.NavigationHistory
324325
import com.duckduckgo.js.messaging.api.JsCallbackData
326+
import com.duckduckgo.js.messaging.api.SubscriptionEventData
325327
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed
326328
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.MALWARE
327329
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.PHISHING
@@ -364,6 +366,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
364366
import kotlinx.coroutines.FlowPreview
365367
import kotlinx.coroutines.Job
366368
import kotlinx.coroutines.async
369+
import kotlinx.coroutines.channels.Channel
367370
import kotlinx.coroutines.delay
368371
import kotlinx.coroutines.flow.Flow
369372
import kotlinx.coroutines.flow.MutableStateFlow
@@ -382,6 +385,7 @@ import kotlinx.coroutines.flow.flowOn
382385
import kotlinx.coroutines.flow.launchIn
383386
import kotlinx.coroutines.flow.map
384387
import kotlinx.coroutines.flow.onEach
388+
import kotlinx.coroutines.flow.receiveAsFlow
385389
import kotlinx.coroutines.flow.stateIn
386390
import kotlinx.coroutines.launch
387391
import kotlinx.coroutines.withContext
@@ -488,6 +492,7 @@ class BrowserTabViewModel @Inject constructor(
488492
private val webViewCompatWrapper: WebViewCompatWrapper,
489493
private val addressBarTrackersAnimationFeatureToggle: AddressBarTrackersAnimationFeatureToggle,
490494
private val autoconsentPixelManager: AutoconsentPixelManager,
495+
private val contentScopeScriptsSubscriptionEventPluginPoint: PluginPoint<ContentScopeScriptsSubscriptionEventPlugin>
491496
) : ViewModel(),
492497
WebViewClientListener,
493498
EditSavedSiteListener,
@@ -537,6 +542,9 @@ class BrowserTabViewModel @Inject constructor(
537542

538543
private var activeExperiments: List<Toggle>? = null
539544

545+
private val _subscriptionEventDataChannel = Channel<SubscriptionEventData>(capacity = Channel.BUFFERED)
546+
val subscriptionEventFlow: Flow<SubscriptionEventData> = _subscriptionEventDataChannel.receiveAsFlow()
547+
540548
data class HiddenBookmarksIds(
541549
val favorites: List<String> = emptyList(),
542550
val bookmarks: List<String> = emptyList(),
@@ -939,6 +947,12 @@ class BrowserTabViewModel @Inject constructor(
939947
lastFullSiteUrlEnabled = settingsDataStore.isFullUrlEnabled
940948
command.value = Command.RefreshOmnibar
941949
}
950+
951+
viewModelScope.launch {
952+
contentScopeScriptsSubscriptionEventPluginPoint.getPlugins().forEach { plugin ->
953+
_subscriptionEventDataChannel.send(plugin.getSubscriptionEventData())
954+
}
955+
}
942956
}
943957

944958
fun onViewVisible() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.contentscopescripts.api
18+
19+
import com.duckduckgo.js.messaging.api.SubscriptionEventData
20+
21+
/**
22+
* Use this interface to create a new plugin that will provide a ContentScopeScriptsSubscriptionEventPlugin and provide
23+
* [SubscriptionEventData] that can be sent to C-S-S
24+
*/
25+
interface ContentScopeScriptsSubscriptionEventPlugin {
26+
27+
/**
28+
* This method returns a [SubscriptionEventData] that can be sent to C-S-S
29+
* @return [SubscriptionEventData]
30+
*/
31+
fun getSubscriptionEventData(): SubscriptionEventData
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2022 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.contentscopescripts.impl
18+
19+
import com.duckduckgo.anvil.annotations.ContributesPluginPoint
20+
import com.duckduckgo.contentscopescripts.api.ContentScopeScriptsSubscriptionEventPlugin
21+
import com.duckduckgo.di.scopes.AppScope
22+
23+
@ContributesPluginPoint(
24+
scope = AppScope::class,
25+
boundType = ContentScopeScriptsSubscriptionEventPlugin::class,
26+
)
27+
@Suppress("unused")
28+
interface ContentScopeScriptsSubscriptionEventPluginPoint
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.duckchat.impl.ui.settings
18+
19+
import com.duckduckgo.contentscopescripts.api.ContentScopeScriptsSubscriptionEventPlugin
20+
import com.duckduckgo.di.scopes.AppScope
21+
import com.duckduckgo.duckchat.api.DuckChat
22+
import com.duckduckgo.js.messaging.api.SubscriptionEventData
23+
import com.squareup.anvil.annotations.ContributesMultibinding
24+
import org.json.JSONObject
25+
import javax.inject.Inject
26+
27+
@ContributesMultibinding(AppScope::class)
28+
class DuckChatEnabledContentScopeScriptsSubscriptionEventPlugin @Inject constructor(
29+
private val duckChat: DuckChat,
30+
) : ContentScopeScriptsSubscriptionEventPlugin {
31+
32+
override fun getSubscriptionEventData(): SubscriptionEventData =
33+
SubscriptionEventData(
34+
featureName = "serpSettings",
35+
subscriptionName = "nativeDuckAiSettingChanged",
36+
params = JSONObject().apply {
37+
put("enabled", duckChat.isEnabled())
38+
},
39+
)
40+
}

settings/settings-api/src/main/java/com/duckduckgo/settings/api/SettingsPageFeature.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ interface SettingsPageFeature {
3535
@Toggle.InternalAlwaysEnabled
3636
fun hideAiGeneratedImagesOption(): Toggle
3737

38-
@Toggle.DefaultValue(DefaultFeatureValue.FALSE)
38+
@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
3939
fun serpSettingsSync(): Toggle
4040
}

settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/SettingsWebViewActivity.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import com.duckduckgo.settings.impl.databinding.ActivitySettingsWebviewBinding
3939
import kotlinx.coroutines.flow.launchIn
4040
import kotlinx.coroutines.flow.onEach
4141
import kotlinx.coroutines.launch
42+
import logcat.logcat
4243
import org.json.JSONObject
4344
import javax.inject.Inject
4445
import javax.inject.Named
@@ -84,6 +85,13 @@ class SettingsWebViewActivity : DuckDuckGoActivity() {
8485

8586
viewModel.onStart(url)
8687
}
88+
89+
observeSubscriptionEventDataChannel()
90+
}
91+
92+
override fun onResume() {
93+
super.onResume()
94+
viewModel.onResume()
8795
}
8896

8997
private fun setupBackPressedDispatcher() {
@@ -115,6 +123,13 @@ class SettingsWebViewActivity : DuckDuckGoActivity() {
115123
}
116124
}
117125

126+
private fun observeSubscriptionEventDataChannel() {
127+
viewModel.subscriptionEventFlow.onEach { subscriptionEventData ->
128+
logcat { "SERP-Settings: Sending subscription event data to content scope scripts: $subscriptionEventData" }
129+
contentScopeScripts.sendSubscriptionEvent(subscriptionEventData)
130+
}.launchIn(lifecycleScope)
131+
}
132+
118133
private fun exit() {
119134
binding.settingsWebView.stopLoading()
120135
binding.root.removeView(binding.settingsWebView)

settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/SettingsWebViewViewModel.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,30 @@ package com.duckduckgo.settings.impl
1919
import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.viewModelScope
2121
import com.duckduckgo.anvil.annotations.ContributesViewModel
22+
import com.duckduckgo.common.utils.plugins.PluginPoint
23+
import com.duckduckgo.contentscopescripts.api.ContentScopeScriptsSubscriptionEventPlugin
2224
import com.duckduckgo.di.scopes.ActivityScope
25+
import com.duckduckgo.js.messaging.api.SubscriptionEventData
2326
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
2427
import kotlinx.coroutines.channels.Channel
28+
import kotlinx.coroutines.flow.Flow
2529
import kotlinx.coroutines.flow.receiveAsFlow
2630
import kotlinx.coroutines.launch
2731
import logcat.LogPriority
2832
import logcat.logcat
2933
import javax.inject.Inject
3034

3135
@ContributesViewModel(ActivityScope::class)
32-
class SettingsWebViewViewModel @Inject constructor() : ViewModel() {
36+
class SettingsWebViewViewModel @Inject constructor(
37+
private val contentScopeScriptsSubscriptionEventPluginPoint: PluginPoint<ContentScopeScriptsSubscriptionEventPlugin>
38+
) : ViewModel() {
39+
3340
private val commandChannel = Channel<Command>(capacity = 1, onBufferOverflow = DROP_OLDEST)
3441
val commands = commandChannel.receiveAsFlow()
3542

43+
private val _subscriptionEventDataChannel = Channel<SubscriptionEventData>(capacity = Channel.BUFFERED)
44+
val subscriptionEventFlow: Flow<SubscriptionEventData> = _subscriptionEventDataChannel.receiveAsFlow()
45+
3646
sealed class Command {
3747
data class LoadUrl(
3848
val url: String,
@@ -50,6 +60,18 @@ class SettingsWebViewViewModel @Inject constructor() : ViewModel() {
5060
}
5161
}
5262

63+
fun onResume() {
64+
processContentScopeScriptsSubscriptionEventPlugin()
65+
}
66+
67+
private fun processContentScopeScriptsSubscriptionEventPlugin() {
68+
viewModelScope.launch {
69+
contentScopeScriptsSubscriptionEventPluginPoint.getPlugins().forEach { plugin ->
70+
_subscriptionEventDataChannel.send(plugin.getSubscriptionEventData())
71+
}
72+
}
73+
}
74+
5375
private fun sendCommand(command: Command) {
5476
viewModelScope.launch {
5577
commandChannel.send(command)

0 commit comments

Comments
 (0)