Skip to content

Commit

Permalink
fix(android): messaging regression for multiple webviews (#3394)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kudo committed Apr 15, 2024
1 parent 74de1f4 commit 2379ad0
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 21 deletions.
14 changes: 14 additions & 0 deletions example/App.tsx
Expand Up @@ -18,6 +18,7 @@ import Uploads from './examples/Uploads';
import Injection from './examples/Injection';
import LocalPageLoad from './examples/LocalPageLoad';
import Messaging from './examples/Messaging';
import MultiMessaging from './examples/MultiMessaging';
import NativeWebpage from './examples/NativeWebpage';
import ApplePay from './examples/ApplePay';
import CustomMenu from './examples/CustomMenu';
Expand All @@ -34,6 +35,14 @@ const TESTS = {
return <Messaging />;
},
},
MultiMessaging: {
title: 'MultiMessaging',
testId: 'multimessaging',
description: 'Multi js-webview postMessage messaging test',
render() {
return <MultiMessaging />;
},
},
Alerts: {
title: 'Alerts',
testId: 'alerts',
Expand Down Expand Up @@ -224,6 +233,11 @@ export default class App extends Component<Props, State> {
title="Messaging"
onPress={() => this._changeTest('Messaging')}
/>
<Button
testID="testType_multimessaging"
title="MultiMessaging"
onPress={() => this._changeTest('MultiMessaging')}
/>
<Button
testID="testType_nativeWebpage"
title="NativeWebpage"
Expand Down
104 changes: 104 additions & 0 deletions example/examples/MultiMessaging.tsx
@@ -0,0 +1,104 @@
/* eslint-disable react-native/no-inline-styles */
import React from 'react';
import { View, Alert, TextInput } from 'react-native';

import WebView from 'react-native-webview';

const HTML = `<!DOCTYPE html>\n
<html>
<head>
<title>Messaging</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=160, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
</style>
</head>
<body>
<button onclick="sendPostMessage()">Send post message from JS to WebView</button>
<p id="demo"></p>
<p id="test">Nothing received yet</p>
<script>
function sendPostMessage() {
window.ReactNativeWebView.postMessage('Message from JS');
}
window.addEventListener('message',function(event){
document.getElementById('test').innerHTML = event.data;
console.log("Message received from RN: ",event.data);
},false);
document.addEventListener('message',function(event){
document.getElementById('test').innerHTML = event.data;
console.log("Message received from RN: ",event.data);
},false);
</script>
</body>
</html>`;

export default function MultiMessaging() {
const webView = React.useRef<WebView | null>(null);
const webView2 = React.useRef<WebView | null>(null);

return (
<View style={{ flex: 1, flexDirection: 'row' }}>
<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
<TextInput
style={{
height: 40,
borderColor: 'gray',
borderWidth: 1,
margin: 8,
}}
onSubmitEditing={(e) => {
webView.current?.postMessage(e.nativeEvent.text);
}}
/>
<WebView
ref={webView}
source={{ html: HTML }}
onLoadEnd={() => {
webView.current?.postMessage('Hello from RN');
}}
automaticallyAdjustContentInsets={false}
onMessage={(e: { nativeEvent: { data?: string } }) => {
Alert.alert('Message received from JS: ', e.nativeEvent.data);
}}
/>
</View>

<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
<TextInput
style={{
height: 40,
borderColor: 'gray',
borderWidth: 1,
margin: 8,
}}
onSubmitEditing={(e) => {
webView2.current?.postMessage(e.nativeEvent.text);
}}
/>
<WebView
ref={webView2}
source={{
html: HTML.replace(/from JS/g, 'from JS2'),
}}
onLoadEnd={() => {
webView2.current?.postMessage('Hello from RN2');
}}
automaticallyAdjustContentInsets={false}
onMessage={(e: { nativeEvent: { data?: string } }) => {
Alert.alert('Message received from JS2: ', e.nativeEvent.data);
}}
/>
</View>
</View>
);
}
106 changes: 106 additions & 0 deletions example/examples/MultiMessaging.windows.tsx
@@ -0,0 +1,106 @@
/* eslint-disable react-native/no-inline-styles */
import React from 'react';
import { View, Alert, TextInput } from 'react-native';

import WebView from 'react-native-webview';

const HTML = `<!DOCTYPE html>\n
<html>
<head>
<title>Messaging</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=160, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
</style>
</head>
<body>
<button onclick="sendPostMessage()">Send post message from JS to WebView</button>
<p id="demo"></p>
<p id="test">Nothing received yet</p>
<script>
function sendPostMessage() {
window.postMessage('Message from JS');
}
window.addEventListener('message',function(event){
document.getElementById('test').innerHTML = event.data;
console.log("Message received from RN: ",event.data);
},false);
document.addEventListener('message',function(event){
document.getElementById('test').innerHTML = event.data;
console.log("Message received from RN: ",event.data);
},false);
</script>
</body>
</html>`;

export default function MultiMessaging() {
const webView = React.useRef<WebView | null>(null);
const webView2 = React.useRef<WebView | null>(null);

return (
<View style={{ flex: 1, flexDirection: 'row' }}>
<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
<TextInput
style={{
height: 40,
borderColor: 'gray',
borderWidth: 1,
margin: 8,
}}
onSubmitEditing={(e) => {
webView.current?.postMessage(e.nativeEvent.text);
}}
/>
<WebView
ref={webView}
source={{ html: HTML }}
onLoadEnd={() => {
webView.current?.postMessage('Hello from RN');
}}
automaticallyAdjustContentInsets={false}
onMessage={(e: { nativeEvent: { data?: string } }) => {
Alert.alert('Message received from JS: ', e.nativeEvent.data);
}}
useWebView2
/>
</View>

<View style={{ flexDirection: 'column', flex: 1, margin: 4 }}>
<TextInput
style={{
height: 40,
borderColor: 'gray',
borderWidth: 1,
margin: 8,
}}
onSubmitEditing={(e) => {
webView2.current?.postMessage(e.nativeEvent.text);
}}
/>
<WebView
ref={webView2}
source={{
html: HTML.replace(/from JS/g, 'from JS2'),
}}
onLoadEnd={() => {
webView2.current?.postMessage('Hello from RN2');
}}
automaticallyAdjustContentInsets={false}
onMessage={(e: { nativeEvent: { data?: string } }) => {
Alert.alert('Message received from JS2: ', e.nativeEvent.data);
}}
useWebView2
/>
</View>
</View>
);
}
63 changes: 42 additions & 21 deletions src/WebView.android.tsx
Expand Up @@ -4,13 +4,13 @@ import React, {
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from 'react';

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

import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter';

import invariant from 'invariant';

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

const { resolveAssetSource } = Image;

const directEventEmitter = new EventEmitter();

const registerCallableModule: (name: string, module: Object) => void =
// `registerCallableModule()` is available in React Native 0.74 and above.
// Fallback to use `BatchedBridge.registerCallableModule()` for older versions.

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

registerCallableModule('RNCWebViewMessagingModule', {
onShouldStartLoadWithRequest: (
event: ShouldStartLoadRequestEvent & { messagingModuleName?: string }
) => {
directEventEmitter.emit('onShouldStartLoadWithRequest', event);
},
onMessage: (
event: WebViewMessageEvent & { messagingModuleName?: string }
) => {
directEventEmitter.emit('onMessage', event);
},
});

/**
* A simple counter to uniquely identify WebView instances. Do not use this for anything else.
*/
Expand Down Expand Up @@ -168,33 +183,39 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
[setViewState, webViewRef]
);

const directEventCallbacks = useMemo(
() => ({
onShouldStartLoadWithRequest: (
event: ShouldStartLoadRequestEvent & { messagingModuleName?: string }
) => {
if (event.messagingModuleName === messagingModuleName) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { messagingModuleName: _, ...rest } = event;
onShouldStartLoadWithRequest(rest);
useEffect(() => {
const onShouldStartLoadWithRequestSubscription =
directEventEmitter.addListener(
'onShouldStartLoadWithRequest',
(
event: ShouldStartLoadRequestEvent & {
messagingModuleName?: string;
}
) => {
if (event.messagingModuleName === messagingModuleName) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { messagingModuleName: _, ...rest } = event;
onShouldStartLoadWithRequest(rest);
}
}
},
onMessage: (
event: WebViewMessageEvent & { messagingModuleName?: string }
) => {
);

const onMessageSubscription = directEventEmitter.addListener(
'onMessage',
(event: WebViewMessageEvent & { messagingModuleName?: string }) => {
if (event.messagingModuleName === messagingModuleName) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { messagingModuleName: _, ...rest } = event;
onMessage(rest);
}
},
}),
[messagingModuleName, onMessage, onShouldStartLoadWithRequest]
);
}
);

useEffect(() => {
registerCallableModule('RNCWebViewMessagingModule', directEventCallbacks);
}, [messagingModuleName, directEventCallbacks]);
return () => {
onShouldStartLoadWithRequestSubscription.remove();
onMessageSubscription.remove();
};
}, [messagingModuleName, onMessage, onShouldStartLoadWithRequest]);

let otherView: ReactElement | undefined;
if (viewState === 'LOADING') {
Expand Down

0 comments on commit 2379ad0

Please sign in to comment.