Skip to content

Commit 2379ad0

Browse files
authored
fix(android): messaging regression for multiple webviews (react-native-webview#3394)
1 parent 74de1f4 commit 2379ad0

File tree

4 files changed

+266
-21
lines changed

4 files changed

+266
-21
lines changed

example/App.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Uploads from './examples/Uploads';
1818
import Injection from './examples/Injection';
1919
import LocalPageLoad from './examples/LocalPageLoad';
2020
import Messaging from './examples/Messaging';
21+
import MultiMessaging from './examples/MultiMessaging';
2122
import NativeWebpage from './examples/NativeWebpage';
2223
import ApplePay from './examples/ApplePay';
2324
import CustomMenu from './examples/CustomMenu';
@@ -34,6 +35,14 @@ const TESTS = {
3435
return <Messaging />;
3536
},
3637
},
38+
MultiMessaging: {
39+
title: 'MultiMessaging',
40+
testId: 'multimessaging',
41+
description: 'Multi js-webview postMessage messaging test',
42+
render() {
43+
return <MultiMessaging />;
44+
},
45+
},
3746
Alerts: {
3847
title: 'Alerts',
3948
testId: 'alerts',
@@ -224,6 +233,11 @@ export default class App extends Component<Props, State> {
224233
title="Messaging"
225234
onPress={() => this._changeTest('Messaging')}
226235
/>
236+
<Button
237+
testID="testType_multimessaging"
238+
title="MultiMessaging"
239+
onPress={() => this._changeTest('MultiMessaging')}
240+
/>
227241
<Button
228242
testID="testType_nativeWebpage"
229243
title="NativeWebpage"

example/examples/MultiMessaging.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/* eslint-disable react-native/no-inline-styles */
2+
import React from 'react';
3+
import { View, Alert, TextInput } from 'react-native';
4+
5+
import WebView from 'react-native-webview';
6+
7+
const HTML = `<!DOCTYPE html>\n
8+
<html>
9+
<head>
10+
<title>Messaging</title>
11+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
12+
<meta name="viewport" content="width=160, user-scalable=no">
13+
<style type="text/css">
14+
body {
15+
margin: 0;
16+
padding: 0;
17+
font: 62.5% arial, sans-serif;
18+
background: #ccc;
19+
}
20+
</style>
21+
</head>
22+
<body>
23+
<button onclick="sendPostMessage()">Send post message from JS to WebView</button>
24+
<p id="demo"></p>
25+
<p id="test">Nothing received yet</p>
26+
27+
<script>
28+
function sendPostMessage() {
29+
window.ReactNativeWebView.postMessage('Message from JS');
30+
}
31+
32+
window.addEventListener('message',function(event){
33+
document.getElementById('test').innerHTML = event.data;
34+
console.log("Message received from RN: ",event.data);
35+
},false);
36+
document.addEventListener('message',function(event){
37+
document.getElementById('test').innerHTML = event.data;
38+
console.log("Message received from RN: ",event.data);
39+
},false);
40+
41+
</script>
42+
</body>
43+
</html>`;
44+
45+
export default function MultiMessaging() {
46+
const webView = React.useRef<WebView | null>(null);
47+
const webView2 = React.useRef<WebView | null>(null);
48+
49+
return (
50+
<View style={{ flex: 1, flexDirection: 'row' }}>
51+
<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
52+
<TextInput
53+
style={{
54+
height: 40,
55+
borderColor: 'gray',
56+
borderWidth: 1,
57+
margin: 8,
58+
}}
59+
onSubmitEditing={(e) => {
60+
webView.current?.postMessage(e.nativeEvent.text);
61+
}}
62+
/>
63+
<WebView
64+
ref={webView}
65+
source={{ html: HTML }}
66+
onLoadEnd={() => {
67+
webView.current?.postMessage('Hello from RN');
68+
}}
69+
automaticallyAdjustContentInsets={false}
70+
onMessage={(e: { nativeEvent: { data?: string } }) => {
71+
Alert.alert('Message received from JS: ', e.nativeEvent.data);
72+
}}
73+
/>
74+
</View>
75+
76+
<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
77+
<TextInput
78+
style={{
79+
height: 40,
80+
borderColor: 'gray',
81+
borderWidth: 1,
82+
margin: 8,
83+
}}
84+
onSubmitEditing={(e) => {
85+
webView2.current?.postMessage(e.nativeEvent.text);
86+
}}
87+
/>
88+
<WebView
89+
ref={webView2}
90+
source={{
91+
html: HTML.replace(/from JS/g, 'from JS2'),
92+
}}
93+
onLoadEnd={() => {
94+
webView2.current?.postMessage('Hello from RN2');
95+
}}
96+
automaticallyAdjustContentInsets={false}
97+
onMessage={(e: { nativeEvent: { data?: string } }) => {
98+
Alert.alert('Message received from JS2: ', e.nativeEvent.data);
99+
}}
100+
/>
101+
</View>
102+
</View>
103+
);
104+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/* eslint-disable react-native/no-inline-styles */
2+
import React from 'react';
3+
import { View, Alert, TextInput } from 'react-native';
4+
5+
import WebView from 'react-native-webview';
6+
7+
const HTML = `<!DOCTYPE html>\n
8+
<html>
9+
<head>
10+
<title>Messaging</title>
11+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
12+
<meta name="viewport" content="width=160, user-scalable=no">
13+
<style type="text/css">
14+
body {
15+
margin: 0;
16+
padding: 0;
17+
font: 62.5% arial, sans-serif;
18+
background: #ccc;
19+
}
20+
</style>
21+
</head>
22+
<body>
23+
<button onclick="sendPostMessage()">Send post message from JS to WebView</button>
24+
<p id="demo"></p>
25+
<p id="test">Nothing received yet</p>
26+
27+
<script>
28+
function sendPostMessage() {
29+
window.postMessage('Message from JS');
30+
}
31+
32+
window.addEventListener('message',function(event){
33+
document.getElementById('test').innerHTML = event.data;
34+
console.log("Message received from RN: ",event.data);
35+
},false);
36+
document.addEventListener('message',function(event){
37+
document.getElementById('test').innerHTML = event.data;
38+
console.log("Message received from RN: ",event.data);
39+
},false);
40+
41+
</script>
42+
</body>
43+
</html>`;
44+
45+
export default function MultiMessaging() {
46+
const webView = React.useRef<WebView | null>(null);
47+
const webView2 = React.useRef<WebView | null>(null);
48+
49+
return (
50+
<View style={{ flex: 1, flexDirection: 'row' }}>
51+
<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
52+
<TextInput
53+
style={{
54+
height: 40,
55+
borderColor: 'gray',
56+
borderWidth: 1,
57+
margin: 8,
58+
}}
59+
onSubmitEditing={(e) => {
60+
webView.current?.postMessage(e.nativeEvent.text);
61+
}}
62+
/>
63+
<WebView
64+
ref={webView}
65+
source={{ html: HTML }}
66+
onLoadEnd={() => {
67+
webView.current?.postMessage('Hello from RN');
68+
}}
69+
automaticallyAdjustContentInsets={false}
70+
onMessage={(e: { nativeEvent: { data?: string } }) => {
71+
Alert.alert('Message received from JS: ', e.nativeEvent.data);
72+
}}
73+
useWebView2
74+
/>
75+
</View>
76+
77+
<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
78+
<TextInput
79+
style={{
80+
height: 40,
81+
borderColor: 'gray',
82+
borderWidth: 1,
83+
margin: 8,
84+
}}
85+
onSubmitEditing={(e) => {
86+
webView2.current?.postMessage(e.nativeEvent.text);
87+
}}
88+
/>
89+
<WebView
90+
ref={webView2}
91+
source={{
92+
html: HTML.replace(/from JS/g, 'from JS2'),
93+
}}
94+
onLoadEnd={() => {
95+
webView2.current?.postMessage('Hello from RN2');
96+
}}
97+
automaticallyAdjustContentInsets={false}
98+
onMessage={(e: { nativeEvent: { data?: string } }) => {
99+
Alert.alert('Message received from JS2: ', e.nativeEvent.data);
100+
}}
101+
useWebView2
102+
/>
103+
</View>
104+
</View>
105+
);
106+
}

src/WebView.android.tsx

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import React, {
44
useCallback,
55
useEffect,
66
useImperativeHandle,
7-
useMemo,
87
useRef,
98
} from 'react';
109

1110
import { Image, View, ImageSourcePropType, HostComponent } from 'react-native';
1211

1312
import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
13+
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';
1414

1515
import invariant from 'invariant';
1616

@@ -33,13 +33,28 @@ import styles from './WebView.styles';
3333

3434
const { resolveAssetSource } = Image;
3535

36+
const directEventEmitter = new EventEmitter();
37+
3638
const registerCallableModule: (name: string, module: Object) => void =
3739
// `registerCallableModule()` is available in React Native 0.74 and above.
3840
// Fallback to use `BatchedBridge.registerCallableModule()` for older versions.
3941

4042
require('react-native').registerCallableModule ??
4143
BatchedBridge.registerCallableModule.bind(BatchedBridge);
4244

45+
registerCallableModule('RNCWebViewMessagingModule', {
46+
onShouldStartLoadWithRequest: (
47+
event: ShouldStartLoadRequestEvent & { messagingModuleName?: string }
48+
) => {
49+
directEventEmitter.emit('onShouldStartLoadWithRequest', event);
50+
},
51+
onMessage: (
52+
event: WebViewMessageEvent & { messagingModuleName?: string }
53+
) => {
54+
directEventEmitter.emit('onMessage', event);
55+
},
56+
});
57+
4358
/**
4459
* A simple counter to uniquely identify WebView instances. Do not use this for anything else.
4560
*/
@@ -168,33 +183,39 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
168183
[setViewState, webViewRef]
169184
);
170185

171-
const directEventCallbacks = useMemo(
172-
() => ({
173-
onShouldStartLoadWithRequest: (
174-
event: ShouldStartLoadRequestEvent & { messagingModuleName?: string }
175-
) => {
176-
if (event.messagingModuleName === messagingModuleName) {
177-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
178-
const { messagingModuleName: _, ...rest } = event;
179-
onShouldStartLoadWithRequest(rest);
186+
useEffect(() => {
187+
const onShouldStartLoadWithRequestSubscription =
188+
directEventEmitter.addListener(
189+
'onShouldStartLoadWithRequest',
190+
(
191+
event: ShouldStartLoadRequestEvent & {
192+
messagingModuleName?: string;
193+
}
194+
) => {
195+
if (event.messagingModuleName === messagingModuleName) {
196+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
197+
const { messagingModuleName: _, ...rest } = event;
198+
onShouldStartLoadWithRequest(rest);
199+
}
180200
}
181-
},
182-
onMessage: (
183-
event: WebViewMessageEvent & { messagingModuleName?: string }
184-
) => {
201+
);
202+
203+
const onMessageSubscription = directEventEmitter.addListener(
204+
'onMessage',
205+
(event: WebViewMessageEvent & { messagingModuleName?: string }) => {
185206
if (event.messagingModuleName === messagingModuleName) {
186207
// eslint-disable-next-line @typescript-eslint/no-unused-vars
187208
const { messagingModuleName: _, ...rest } = event;
188209
onMessage(rest);
189210
}
190-
},
191-
}),
192-
[messagingModuleName, onMessage, onShouldStartLoadWithRequest]
193-
);
211+
}
212+
);
194213

195-
useEffect(() => {
196-
registerCallableModule('RNCWebViewMessagingModule', directEventCallbacks);
197-
}, [messagingModuleName, directEventCallbacks]);
214+
return () => {
215+
onShouldStartLoadWithRequestSubscription.remove();
216+
onMessageSubscription.remove();
217+
};
218+
}, [messagingModuleName, onMessage, onShouldStartLoadWithRequest]);
198219

199220
let otherView: ReactElement | undefined;
200221
if (viewState === 'LOADING') {

0 commit comments

Comments
 (0)