Skip to content

Commit e4d29da

Browse files
committed
Encapsulate injection checks on a separate class
1 parent 2cd2a24 commit e4d29da

4 files changed

Lines changed: 124 additions & 47 deletions

File tree

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/AdBlockingExtensionJsInjectorPlugin.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.duckduckgo.adblocking.impl
1919
import android.webkit.WebView
2020
import androidx.annotation.UiThread
2121
import androidx.core.net.toUri
22-
import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature
22+
import com.duckduckgo.adblocking.impl.domain.AdBlockingStatusChecker
2323
import com.duckduckgo.app.browser.Domain
2424
import com.duckduckgo.app.browser.UriString
2525
import com.duckduckgo.app.di.AppCoroutineScope
@@ -40,7 +40,7 @@ import javax.inject.Inject
4040
@SingleInstanceIn(AppScope::class)
4141
@ContributesMultibinding(AppScope::class)
4242
class AdBlockingExtensionJsInjectorPlugin @Inject constructor(
43-
private val feature: AdBlockingExtensionFeature,
43+
private val statusChecker: AdBlockingStatusChecker,
4444
repository: AdBlockingExtensionRepository,
4545
@AppCoroutineScope appScope: CoroutineScope,
4646
) : JsInjectorPlugin {
@@ -57,12 +57,8 @@ class AdBlockingExtensionJsInjectorPlugin @Inject constructor(
5757
isDesktopMode: Boolean?,
5858
activeExperiments: List<Toggle>,
5959
) {
60-
if (!feature.isDiscoverable().isEnabled()) {
61-
logcat { "Feature not discoverable, skipping" }
62-
return
63-
}
64-
if (!feature.self().isEnabled()) {
65-
logcat { "Feature not operational, skipping" }
60+
if (!statusChecker.canInject()) {
61+
logcat { "Status checker rejected injection, skipping" }
6662
return
6763
}
6864
val uri = url?.toUri() ?: return
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2026 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.adblocking.impl.domain
18+
19+
import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature
20+
import com.duckduckgo.di.scopes.AppScope
21+
import com.squareup.anvil.annotations.ContributesBinding
22+
import dagger.SingleInstanceIn
23+
import logcat.logcat
24+
import javax.inject.Inject
25+
26+
interface AdBlockingStatusChecker {
27+
fun canInject(): Boolean
28+
}
29+
30+
@SingleInstanceIn(AppScope::class)
31+
@ContributesBinding(AppScope::class)
32+
class RealAdBlockingStatusChecker @Inject constructor(
33+
private val feature: AdBlockingExtensionFeature,
34+
) : AdBlockingStatusChecker {
35+
36+
override fun canInject(): Boolean {
37+
if (!feature.isDiscoverable().isEnabled()) {
38+
logcat { "Feature not discoverable" }
39+
return false
40+
}
41+
if (!feature.self().isEnabled()) {
42+
logcat { "Feature not operational" }
43+
return false
44+
}
45+
return true
46+
}
47+
}

ad-blocking/ad-blocking-impl/src/test/java/com/duckduckgo/adblocking/impl/AdBlockingExtensionJsInjectorPluginTest.kt

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ package com.duckduckgo.adblocking.impl
1818

1919
import android.webkit.WebView
2020
import androidx.test.ext.junit.runners.AndroidJUnit4
21-
import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature
22-
import com.duckduckgo.feature.toggles.api.Toggle
21+
import com.duckduckgo.adblocking.impl.domain.AdBlockingStatusChecker
2322
import kotlinx.coroutines.CoroutineScope
2423
import kotlinx.coroutines.ExperimentalCoroutinesApi
2524
import kotlinx.coroutines.cancel
@@ -41,19 +40,11 @@ import org.mockito.kotlin.verify
4140
@RunWith(AndroidJUnit4::class)
4241
class AdBlockingExtensionJsInjectorPluginTest {
4342

44-
private var discoverableEnabled = true
45-
private var operationalEnabled = true
43+
private var canInject = true
4644
private val scriptletsFlow = MutableStateFlow<List<Scriptlet>>(emptyList())
4745

48-
private val isDiscoverableToggle: Toggle = mock {
49-
on { isEnabled() } doAnswer { discoverableEnabled }
50-
}
51-
private val selfToggle: Toggle = mock {
52-
on { isEnabled() } doAnswer { operationalEnabled }
53-
}
54-
private val feature: AdBlockingExtensionFeature = mock {
55-
on { isDiscoverable() } doReturn isDiscoverableToggle
56-
on { self() } doReturn selfToggle
46+
private val statusChecker: AdBlockingStatusChecker = mock {
47+
on { canInject() } doAnswer { canInject }
5748
}
5849
private val repository: AdBlockingExtensionRepository = mock {
5950
on { scriptletsFlow() } doReturn scriptletsFlow
@@ -69,7 +60,7 @@ class AdBlockingExtensionJsInjectorPluginTest {
6960

7061
private val plugin by lazy {
7162
AdBlockingExtensionJsInjectorPlugin(
72-
feature = feature,
63+
statusChecker = statusChecker,
7364
repository = repository,
7465
appScope = testScope,
7566
)
@@ -141,18 +132,8 @@ class AdBlockingExtensionJsInjectorPluginTest {
141132
}
142133

143134
@Test
144-
fun whenKillSwitchIsOffThenScriptIsNotInjected() {
145-
discoverableEnabled = false
146-
scriptletsFlow.value = singleScriptlet
147-
148-
plugin.onPageStarted(webView, url = "https://youtube.com/page", isDesktopMode = null)
149-
150-
verify(webView, never()).evaluateJavascript(any(), isNull())
151-
}
152-
153-
@Test
154-
fun whenOperationalIsOffThenScriptIsNotInjected() {
155-
operationalEnabled = false
135+
fun whenStatusCheckerRejectsThenScriptIsNotInjected() {
136+
canInject = false
156137
scriptletsFlow.value = singleScriptlet
157138

158139
plugin.onPageStarted(webView, url = "https://youtube.com/page", isDesktopMode = null)
@@ -202,22 +183,11 @@ class AdBlockingExtensionJsInjectorPluginTest {
202183
}
203184

204185
@Test
205-
fun whenKillSwitchFlipsOffMidSessionThenNextInjectionNoOps() {
206-
scriptletsFlow.value = singleScriptlet
207-
plugin.onPageStarted(webView, url = "https://youtube.com/page", isDesktopMode = null)
208-
209-
discoverableEnabled = false
210-
plugin.onPageStarted(webView, url = "https://youtube.com/page", isDesktopMode = null)
211-
212-
verify(webView).evaluateJavascript(any(), isNull())
213-
}
214-
215-
@Test
216-
fun whenOperationalFlipsOffMidSessionThenNextInjectionNoOps() {
186+
fun whenStatusCheckerStartsRejectingMidSessionThenNextInjectionNoOps() {
217187
scriptletsFlow.value = singleScriptlet
218188
plugin.onPageStarted(webView, url = "https://youtube.com/page", isDesktopMode = null)
219189

220-
operationalEnabled = false
190+
canInject = false
221191
plugin.onPageStarted(webView, url = "https://youtube.com/page", isDesktopMode = null)
222192

223193
verify(webView).evaluateJavascript(any(), isNull())
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2026 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.adblocking.impl.domain
18+
19+
import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature
20+
import com.duckduckgo.feature.toggles.api.Toggle
21+
import org.junit.Assert.assertFalse
22+
import org.junit.Assert.assertTrue
23+
import org.junit.Test
24+
import org.mockito.kotlin.doAnswer
25+
import org.mockito.kotlin.doReturn
26+
import org.mockito.kotlin.mock
27+
28+
class RealAdBlockingStatusCheckerTest {
29+
30+
private var discoverableEnabled = true
31+
private var operationalEnabled = true
32+
33+
private val isDiscoverableToggle: Toggle = mock {
34+
on { isEnabled() } doAnswer { discoverableEnabled }
35+
}
36+
private val selfToggle: Toggle = mock {
37+
on { isEnabled() } doAnswer { operationalEnabled }
38+
}
39+
private val feature: AdBlockingExtensionFeature = mock {
40+
on { isDiscoverable() } doReturn isDiscoverableToggle
41+
on { self() } doReturn selfToggle
42+
}
43+
44+
private val checker = RealAdBlockingStatusChecker(feature)
45+
46+
@Test
47+
fun whenBothRemoteFlagsEnabledThenCanInject() {
48+
assertTrue(checker.canInject())
49+
}
50+
51+
@Test
52+
fun whenDiscoverableFlagDisabledThenCannotInject() {
53+
discoverableEnabled = false
54+
55+
assertFalse(checker.canInject())
56+
}
57+
58+
@Test
59+
fun whenOperationalFlagDisabledThenCannotInject() {
60+
operationalEnabled = false
61+
62+
assertFalse(checker.canInject())
63+
}
64+
}

0 commit comments

Comments
 (0)