Skip to content

Commit

Permalink
Add navigatorInterface + messageBridge support (#5461)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/488551667048375/1208697679300718/f

### Description
- Adds a new package to the `content-scope-scripts-impl` module to
inject the `navigatorInterface` JSON from the privacy config.
- Also adds the related `messageBridge` C-S-S feature which is required
for SERP<->native communication.

### Steps to test this PR

_navigatorInterface_
- [ ] Check out this branch
- [ ] Apply the patch linked in the task
- [ ] Go to
https://privacy-test-pages.site/features/navigator-interface.html
- [ ] Verify the page looks like this:
<img width="348" alt="Screenshot 2025-01-16 at 00 51 01"
src="https://github.com/user-attachments/assets/6bd33830-9ba5-43da-ab32-44e8c9fec690"
/>

_messageBridge_
- [ ] Go to duckduckgo.com
- [ ] Open Chrome and go to `chrome://inspect`
- [ ] In the console, type:
`navigator.duckduckgo.createMessageBridge(“aiChat").notify("helloWorld”)`
- [ ] Verify that you see the following:
<img width="1048" alt="Screenshot 2025-01-16 at 14 59 51"
src="https://github.com/user-attachments/assets/1f482055-727b-4f4b-a002-f8f8a7773e3d"
/>
  • Loading branch information
joshliebe authored Jan 24, 2025
1 parent 05e41b3 commit 766ea1f
Show file tree
Hide file tree
Showing 25 changed files with 1,118 additions and 0 deletions.
6 changes: 6 additions & 0 deletions content-scope-scripts/content-scope-scripts-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ plugins {
id 'com.android.library'
id 'kotlin-android'
id 'com.squareup.anvil'
id 'com.google.devtools.ksp'
}

apply from: "$rootProject.projectDir/gradle/android-library.gradle"
Expand Down Expand Up @@ -47,6 +48,11 @@ dependencies {
implementation Google.dagger
implementation AndroidX.core.ktx

// Room
implementation AndroidX.room.runtime
implementation AndroidX.room.ktx
ksp AndroidX.room.compiler

// Testing dependencies
testImplementation "org.mockito.kotlin:mockito-kotlin:_"
testImplementation Testing.junit4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge

import com.duckduckgo.contentscopescripts.api.ContentScopeConfigPlugin
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.MessageBridgeFeatureName.MessageBridge
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject

@ContributesMultibinding(AppScope::class)
class MessageBridgeContentScopeConfigPlugin @Inject constructor(
private val messageBridgeRepository: MessageBridgeRepository,
) : ContentScopeConfigPlugin {

override fun config(): String {
val featureName = MessageBridge.value
val config = messageBridgeRepository.messageBridgeEntity.json
return "\"$featureName\":$config"
}

override fun preferences(): String? {
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge

enum class MessageBridgeFeatureName(val value: String) {
MessageBridge("messageBridge"),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge

/**
* Convenience method to get the [MessageBridgeFeatureName] from its [String] value
*/
fun messageBridgeFeatureValueOf(value: String): MessageBridgeFeatureName? {
return MessageBridgeFeatureName.entries.find { it.value == value }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge

import com.duckduckgo.contentscopescripts.impl.features.messagebridge.MessageBridgeFeatureName.MessageBridge
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeEntity
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject

@ContributesMultibinding(AppScope::class)
class MessageBridgeFeaturePlugin @Inject constructor(
private val messageBridgeRepository: MessageBridgeRepository,
) : PrivacyFeaturePlugin {

override fun store(featureName: String, jsonString: String): Boolean {
val messageBridgeFeatureName = messageBridgeFeatureValueOf(featureName) ?: return false
if (messageBridgeFeatureName.value == this.featureName) {
val entity = MessageBridgeEntity(json = jsonString)
messageBridgeRepository.updateAll(messageBridgeEntity = entity)
return true
}
return false
}

override val featureName: String = MessageBridge.value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge.di

import android.content.Context
import androidx.room.Room
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.ALL_MIGRATIONS
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeDatabase
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository
import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.RealMessageBridgeRepository
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dagger.SingleInstanceIn
import kotlinx.coroutines.CoroutineScope

@Module
@ContributesTo(AppScope::class)
object MessageBridgeModule {

@SingleInstanceIn(AppScope::class)
@Provides
fun provideMessageBridgeDatabase(context: Context): MessageBridgeDatabase {
return Room.databaseBuilder(context, MessageBridgeDatabase::class.java, "message_bridge.db")
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration()
.addMigrations(*ALL_MIGRATIONS)
.build()
}

@SingleInstanceIn(AppScope::class)
@Provides
fun provideMessageBridgeRepository(
database: MessageBridgeDatabase,
@AppCoroutineScope coroutineScope: CoroutineScope,
dispatcherProvider: DispatcherProvider,
): MessageBridgeRepository {
return RealMessageBridgeRepository(database, coroutineScope, dispatcherProvider)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction

@Dao
abstract class MessageBridgeDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(messageBridgeEntity: MessageBridgeEntity)

@Transaction
open fun updateAll(
messageBridgeEntity: MessageBridgeEntity,
) {
delete()
insert(messageBridgeEntity)
}

@Query("select * from message_bridge")
abstract fun get(): MessageBridgeEntity?

@Query("delete from message_bridge")
abstract fun delete()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.migration.Migration

@Database(
exportSchema = true,
version = 1,
entities = [
MessageBridgeEntity::class,
],
)
abstract class MessageBridgeDatabase : RoomDatabase() {
abstract fun messageBridgeDao(): MessageBridgeDao
}

val ALL_MIGRATIONS = emptyArray<Migration>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "message_bridge")
data class MessageBridgeEntity(
@PrimaryKey val id: Int = 1,
val json: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store

import com.duckduckgo.common.utils.DispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

interface MessageBridgeRepository {
fun updateAll(
messageBridgeEntity: MessageBridgeEntity,
)
var messageBridgeEntity: MessageBridgeEntity
}

class RealMessageBridgeRepository(
val database: MessageBridgeDatabase,
val coroutineScope: CoroutineScope,
val dispatcherProvider: DispatcherProvider,
) : MessageBridgeRepository {

private val messageBridgeDao: MessageBridgeDao = database.messageBridgeDao()
override var messageBridgeEntity = MessageBridgeEntity(json = EMPTY_JSON)

init {
coroutineScope.launch(dispatcherProvider.io()) {
loadToMemory()
}
}

override fun updateAll(messageBridgeEntity: MessageBridgeEntity) {
messageBridgeDao.updateAll(messageBridgeEntity)
loadToMemory()
}

private fun loadToMemory() {
messageBridgeEntity =
messageBridgeDao.get() ?: MessageBridgeEntity(json = EMPTY_JSON)
}

companion object {
const val EMPTY_JSON = "{}"
}
}
Loading

0 comments on commit 766ea1f

Please sign in to comment.