Skip to content

Commit f65d3f7

Browse files
[webview, feat] Full support for new architecture
1 parent efdbca6 commit f65d3f7

12 files changed

Lines changed: 509 additions & 165 deletions

File tree

packages/react-native-webview/android/build.gradle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,13 @@ android {
134134
}
135135

136136
test {
137-
java.srcDir("src/test/kotlin")
137+
java.srcDir('src/test/kotlin')
138+
139+
if (isNewArchitectureEnabled()) {
140+
java.srcDirs += ['src/testNewArch/kotlin']
141+
} else {
142+
java.srcDirs += ['src/testOldArch/kotlin']
143+
}
138144
}
139145
}
140146

@@ -208,6 +214,7 @@ dependencies {
208214
testImplementation "com.github.xgouchet.Elmyr:jvm:1.3.1"
209215
testImplementation "org.mockito.kotlin:mockito-kotlin:5.1.0"
210216
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
217+
testImplementation 'org.json:json:20160810'
211218

212219
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8"
213220
detektPlugins "io.gitlab.arturbosch.detekt:detekt-rules-libraries:1.23.8"
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.reactnative.webview
8+
9+
import android.annotation.SuppressLint
10+
import com.datadog.android.api.SdkCore
11+
import com.datadog.android.webview.WebViewTracking
12+
import com.datadog.reactnative.DatadogSDKWrapperStorage
13+
import com.facebook.react.bridge.ReactContext
14+
import com.facebook.react.uimanager.ThemedReactContext
15+
import com.reactnativecommunity.webview.RNCWebView
16+
import com.reactnativecommunity.webview.RNCWebViewClient
17+
import com.reactnativecommunity.webview.RNCWebViewManager
18+
import com.reactnativecommunity.webview.RNCWebViewWrapper
19+
import org.json.JSONArray
20+
21+
/**
22+
* The entry point to use Datadog auto-instrumented WebView feature.
23+
*/
24+
class DdSdkReactNativeWebViewManager(
25+
private val reactContext: ReactContext
26+
) : RNCWebViewManager() {
27+
// The name used to reference this custom View from React Native.
28+
override fun getName(): String {
29+
return VIEW_NAME
30+
}
31+
32+
/**
33+
* The instance of Datadog SDK Core.
34+
*/
35+
@Volatile private var _datadogCore: SdkCore? = null
36+
val datadogCore: SdkCore?
37+
get() = _datadogCore
38+
39+
init {
40+
DatadogSDKWrapperStorage.addOnInitializedListener { core ->
41+
_datadogCore = core
42+
}
43+
}
44+
45+
/**
46+
* Intercepts the WebView wrapper instance before it is returned and ensures that
47+
* JavaScript is enabled on the underlying WebView. JavaScript must be enabled
48+
* for Datadog WebView tracking to function correctly.
49+
*/
50+
@SuppressLint("SetJavaScriptEnabled")
51+
override fun createViewInstance(context: ThemedReactContext): RNCWebViewWrapper {
52+
val viewInstance = super.createViewInstance(context)
53+
viewInstance.webView.settings.javaScriptEnabled = true
54+
return viewInstance
55+
}
56+
57+
/**
58+
* Intercepts the JavaScript injected before the WebView loads.
59+
*
60+
* In the New Architecture, WebView props from React Native are ignored,
61+
* so this callback is the only reliable place to extract the
62+
* `// #allowedHosts=<JSON>` configuration and apply Datadog WebView tracking.
63+
*/
64+
override fun setInjectedJavaScriptBeforeContentLoaded(
65+
view: RNCWebViewWrapper?,
66+
value: String?
67+
) {
68+
val allowedHosts = value?.let { extractAllowedHosts(it) }
69+
val webView = view?.webView
70+
71+
if (allowedHosts != null && webView != null) {
72+
configureWebViewTracking(webView, allowedHosts)
73+
}
74+
75+
super.setInjectedJavaScriptBeforeContentLoaded(view, value)
76+
}
77+
78+
private fun configureWebViewTracking(webView: RNCWebView, allowedHosts: List<String>) {
79+
val datadogCore = _datadogCore
80+
if (datadogCore != null) {
81+
WebViewTracking.enable(
82+
webView,
83+
allowedHosts = allowedHosts,
84+
sdkCore = datadogCore
85+
)
86+
} else {
87+
DatadogSDKWrapperStorage.addOnInitializedListener { core ->
88+
reactContext.runOnUiQueueThread {
89+
WebViewTracking.enable(
90+
webView,
91+
allowedHosts = allowedHosts,
92+
sdkCore = core
93+
)
94+
}
95+
}
96+
}
97+
}
98+
99+
override fun addEventEmitters(
100+
reactContext: ThemedReactContext,
101+
view: RNCWebViewWrapper
102+
) {
103+
view.webView.webViewClient = RNCWebViewClient()
104+
}
105+
106+
// The name used to reference this custom View from React Native.
107+
companion object {
108+
const val VIEW_NAME = "DdReactNativeWebView"
109+
110+
private fun extractAllowedHosts(input: String): List<String>? {
111+
// Regex that captures everything after "// #allowedHosts="
112+
val regex = Regex("""//\s*#allowedHosts\s*=\s*(.+)""")
113+
114+
val match = regex.find(input) ?: return null
115+
val jsonString = match.groupValues[1].trim()
116+
117+
return try {
118+
val jsonArray = JSONArray(jsonString)
119+
(0 until jsonArray.length()).map { jsonArray.getString(it) }
120+
} catch (e: Exception) {
121+
null
122+
}
123+
}
124+
}
125+
}

packages/react-native-webview/android/src/newarch/com/datadog/reactnative/webview/DdSdkReactNativeWebViewPackage.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* This product includes software developed at Datadog (https://www.datadoghq.com/).
44
* Copyright 2016-Present Datadog, Inc.
55
*/
6-
76
package com.datadog.reactnative.webview
87

98
import com.facebook.react.TurboReactPackage
@@ -12,17 +11,18 @@ import com.facebook.react.bridge.ReactApplicationContext
1211
import com.facebook.react.module.model.ReactModuleInfo
1312
import com.facebook.react.module.model.ReactModuleInfoProvider
1413
import com.facebook.react.uimanager.ViewManager
15-
import com.reactnativecommunity.webview.RNCWebViewManager
14+
import com.reactnativecommunity.webview.RNCWebViewModule
15+
import com.reactnativecommunity.webview.RNCWebViewModuleImpl
1616

1717
class DdSdkReactNativeWebViewPackage : TurboReactPackage() {
1818
override fun createViewManagers(
1919
reactContext: ReactApplicationContext
2020
): MutableList<ViewManager<*,*>> {
2121
return mutableListOf(
22-
RNCWebViewManager()
22+
DdSdkReactNativeWebViewManager(reactContext)
2323
)
2424
}
25-
25+
2626
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
2727
return null
2828
}

packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt renamed to packages/react-native-webview/android/src/test/kotlin/main/com/datadog/reactnative/tools/unit/GenericAssert.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Copyright 2016-Present Datadog, Inc.
55
*/
66

7-
package com.datadog.reactnative.tools.unit
7+
package main.reactnative.tools.unit
88

99
import org.assertj.core.api.AbstractAssert
1010

packages/react-native-webview/android/src/test/kotlin/com/datadog/reactnative/webview/DatadogWebViewTest.kt renamed to packages/react-native-webview/android/src/test/webview/DatadogWebViewTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import com.datadog.android.api.SdkCore
1010
import com.datadog.android.core.InternalSdkCore
1111
import com.datadog.android.webview.WebViewTracking
1212
import com.datadog.reactnative.DatadogSDKWrapperStorage
13-
import com.datadog.reactnative.tools.unit.GenericAssert.Companion.assertThat
13+
import com.datadog.reactnative.webview.DdSdkReactNativeWebViewManager
14+
import main.reactnative.tools.unit.GenericAssert.Companion.assertThat
1415
import com.facebook.react.bridge.JavaOnlyArray
1516
import com.facebook.react.uimanager.ThemedReactContext
1617
import com.reactnativecommunity.webview.RNCWebView
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.reactnative.webview
8+
9+
import com.datadog.android.api.SdkCore
10+
import com.datadog.android.core.InternalSdkCore
11+
import com.datadog.android.webview.WebViewTracking
12+
import com.datadog.reactnative.DatadogSDKWrapperStorage
13+
import main.reactnative.tools.unit.GenericAssert.Companion.assertThat
14+
import com.facebook.react.bridge.JavaOnlyArray
15+
import com.facebook.react.uimanager.ThemedReactContext
16+
import com.reactnativecommunity.webview.RNCWebView
17+
import com.reactnativecommunity.webview.RNCWebViewWrapper
18+
import org.junit.jupiter.api.AfterEach
19+
import org.junit.jupiter.api.BeforeEach
20+
import org.junit.jupiter.api.Test
21+
import org.junit.jupiter.api.extension.ExtendWith
22+
import org.junit.jupiter.api.extension.Extensions
23+
import org.mockito.Mock
24+
import org.mockito.MockedStatic
25+
import org.mockito.Mockito
26+
import org.mockito.Mockito.mock
27+
import org.mockito.junit.jupiter.MockitoExtension
28+
import org.mockito.junit.jupiter.MockitoSettings
29+
import org.mockito.kotlin.any
30+
import org.mockito.kotlin.doReturn
31+
import org.mockito.kotlin.verify
32+
import org.mockito.kotlin.whenever
33+
import org.mockito.quality.Strictness
34+
35+
@Extensions(
36+
ExtendWith(MockitoExtension::class)
37+
)
38+
@MockitoSettings(strictness = Strictness.LENIENT)
39+
internal class DatadogWebViewTest {
40+
41+
@Mock
42+
lateinit var themedReactContext: ThemedReactContext
43+
44+
@Mock
45+
lateinit var datadogCore: InternalSdkCore
46+
47+
private lateinit var webViewTrackingMockedStatic: MockedStatic<WebViewTracking>
48+
49+
@BeforeEach
50+
fun `set up`() {
51+
whenever(themedReactContext.runOnUiQueueThread(any())).thenAnswer { answer ->
52+
answer.getArgument<Runnable>(0).run()
53+
true
54+
}
55+
56+
webViewTrackingMockedStatic = Mockito.mockStatic(WebViewTracking::class.java)
57+
webViewTrackingMockedStatic.`when`<Unit> {
58+
WebViewTracking.enable(
59+
webView = any(), // Mock the WebView parameter
60+
allowedHosts = any(), // Mock the list of allowed hosts
61+
logsSampleRate = any(), // Mock the logsSampleRate parameter
62+
sdkCore = any() // Mock the SdkCore parameter
63+
)
64+
}.then {} // Return Unit as the function has no return value
65+
}
66+
67+
@AfterEach
68+
fun `tear down`() {
69+
webViewTrackingMockedStatic.close()
70+
}
71+
72+
@Test
73+
fun `Datadog Core is set once initialized`() {
74+
val manager = DdSdkReactNativeWebViewManager(themedReactContext)
75+
assertThat(manager.datadogCore).isNull()
76+
77+
DatadogSDKWrapperStorage.notifyOnInitializedListeners(datadogCore)
78+
79+
assertThat(manager.datadogCore).isNotNull()
80+
assertThat(manager.datadogCore).isInstanceOf(SdkCore::class.java)
81+
}
82+
83+
@Test
84+
fun `Registers to SdkCore listener if the SDK is not initialized`() {
85+
// =========
86+
// Given
87+
// =========
88+
val manager = DdSdkReactNativeWebViewManager(themedReactContext)
89+
90+
// When first initialized, the WebView manager core should be null
91+
assertThat(manager.datadogCore).isNull()
92+
93+
// =========
94+
// When
95+
// =========
96+
val rncWebView = mock(RNCWebView::class.java)
97+
val rncWebViewWrapper = mock(RNCWebViewWrapper::class.java)
98+
whenever(rncWebViewWrapper.webView) doReturn rncWebView
99+
100+
// When JS sends allowedHosts through 'injectedJavaScriptBeforeContentLoaded' prop
101+
manager.setInjectedJavaScriptBeforeContentLoaded(
102+
rncWebViewWrapper,
103+
"// #allowedHosts=[\"example.com\",\"test.com\"]"
104+
)
105+
106+
// =========
107+
// Then
108+
// =========
109+
110+
// When we notify listeners that the core is available...
111+
DatadogSDKWrapperStorage.notifyOnInitializedListeners(datadogCore)
112+
113+
// ...the WebView should enable WebView tracking in the UI Thread.
114+
verify(themedReactContext).runOnUiQueueThread(any())
115+
116+
// Native WebView tracking should be called
117+
val arrayList = ArrayList(listOf("example.com", "test.com"))
118+
webViewTrackingMockedStatic.verify {
119+
WebViewTracking.enable(rncWebView, arrayList, 100.0f, datadogCore)
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)