react-native-webview / react-native-webview

React Native Cross-Platform WebView
https://github.com/react-native-community/discussions-and-proposals/pull/3
MIT License
6.31k stars 2.96k forks source link

fix(android): messaging regression for multiple webviews #3394

Closed Kudo closed 2 months ago

Kudo commented 2 months ago

Why

Fixes #3368 which is my regression from #3352

How

JSModule registerCallableModule() does not support multiple listeners. The newer WebView will overwrite the previously registered callbacks. This PR tries to move registerCallableModule() at top as a module singleton. With an intermediate EventEmitter to dispatch events to each WebViews. I tries to import the internal vendored EventEmitter from react-native. If that is not ideal, we could replace as third-party EventEmitter such as fbemitter.

Test Plan

extend the Messsaging.tsx example as two WebViews and check whether messages are sent correctly.

import React, { Component } 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=320, 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>`;

type Props = {};
type State = {};

export default class Messaging extends Component<Props, State> {
  state = {};

  constructor(props) {
    super(props);
    this.webView = React.createRef();
    this.webView2 = React.createRef();
  }

  render() {
    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) => {
              this.webView.current.postMessage(e.nativeEvent.text);
            }}
          />
          <WebView
            ref={this.webView}
            source={{ html: HTML }}
            onLoadEnd={() => {
              this.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) => {
              this.webView2.current.postMessage(e.nativeEvent.text);
            }}
          />
          <WebView
            ref={this.webView2}
            source={{
              html: HTML.replace(/from JS/g, 'from JS2'),
            }}
            onLoadEnd={() => {
              this.webView2.current.postMessage('Hello from RN2');
            }}
            automaticallyAdjustContentInsets={false}
            onMessage={(e: { nativeEvent: { data?: string } }) => {
              Alert.alert('Message received from JS2: ', e.nativeEvent.data);
            }}
          />
        </View>
      </View>
    );
  }
}

demo video: Screen_recording_20240411_013607.webm

TheAlmightyBob commented 2 months ago

Could the "test plan" code actually be added as a new example in the example app?

Kudo commented 2 months ago

Could the "test plan" code actually be added as a new example in the example app?

added as the MultiMessaging example

react-native-community-bot commented 2 months ago

:tada: This PR is included in version 13.8.5 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

TheAlmightyBob commented 2 months ago

Thanks for sorting this out @Kudo !

Kudo commented 2 months ago

awesome! thanks @TheAlmightyBob for publishing the fix!