react-native: [WebView] onMessage failing when there are JS warnings and errors on the page

Description

I’m receiving this error (same as screenshot below):

Setting onMessage on a WebView overrides existing values of window.postMessage, 
but a previous value was defined.

screen shot 2016-11-10 at 3 33 31 pm

That specific error message is found in the code base here: https://github.com/facebook/react-native/blob/master/React/Views/RCTWebView.m#L286

Implementation

I followed the example at this link for onMessage at this link: http://facebook.github.io/react-native/releases/0.37/docs/webview.html#examples

I made a callable respondToOnMessage and injected some JavaScript.

class MessagingTest extends React.Component {

    respondToOnMessage = e =>{
        console.log(e);
    };

    render() {
        const jsCode = `window.postMessage('test');`;

        return (
        <WebView
        injectedJavaScript={jsCode}
        source={uri: 'https://www.foo.com/'}}
        onMessage={this.respondToOnMessage}
        />
        )
    }
}

Reproduction

I load the app with this example and I have it pointed to a website (rather not say which one. Sorry.) And the website is emitting 2 errors into the Chrome console when I go there.

(index):994 A Parser-blocking, cross-origin script, http://example.com/thing.js, is invoked via document.write. This may be blocked by the browser if the device has poor network connectivity.

widgets.js:8 Uncaught TypeError: Cannot set property '[object Array]' of undefined(…)

Other websites like google.com and github.com are just fine. If you want to replicate it, change the uri to yahoo.com

Additional Information

  • React Native version: 0.37
  • Platform: iOS 10.1, iPhone 6 emulator
  • Operating System: Mac OSX Sierra 10.12.1

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 84
  • Comments: 92 (7 by maintainers)

Commits related to this issue

Most upvoted comments

injectedJavascript should instead be the following

(function() {
  var originalPostMessage = window.postMessage;

  var patchedPostMessage = function(message, targetOrigin, transfer) { 
    originalPostMessage(message, targetOrigin, transfer);
  };

  patchedPostMessage.toString = function() { 
    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'); 
  };

  window.postMessage = patchedPostMessage;
})();

And then it works! 😄

And if any one is interested, in my code instead of writing a giant string I did the following

const patchPostMessageFunction = function() {
  var originalPostMessage = window.postMessage;

  var patchedPostMessage = function(message, targetOrigin, transfer) { 
    originalPostMessage(message, targetOrigin, transfer);
  };

  patchedPostMessage.toString = function() { 
    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
  };

  window.postMessage = patchedPostMessage;
};

const patchPostMessageJsCode = '(' + String(patchPostMessageFunction) + ')();';

...

<WebView injectedJavaScript={patchPostMessageJsCode} />

the solution from @kyle-ilantzis works well. I just wrote a patched WebView class for myself, feel free to use. This class also automatically JSON encodes and decodes the messages and tweaked the source attribute to my liking.

import React from 'react'
import { WebView } from 'react-native'

// fix https://github.com/facebook/react-native/issues/10865
const patchPostMessageJsCode = `(${String(function() {
    var originalPostMessage = window.postMessage
    var patchedPostMessage = function(message, targetOrigin, transfer) {
        originalPostMessage(message, targetOrigin, transfer)
    }
    patchedPostMessage.toString = function() {
        return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')
    }
    window.postMessage = patchedPostMessage
})})();`

export default class MessageWebView extends React.Component {
    constructor(props) {
        super(props)
        this.postMessage = this.postMessage.bind(this)
    }
    postMessage(action) {
        this.WebView.postMessage(JSON.stringify(action))
    }
    render() {
        const { html, source, url, onMessage, ...props } = this.props
        return (
            <WebView
                {...props}
                javaScriptEnabled
                injectedJavaScript={patchPostMessageJsCode}
                source={source ? source : html ? { html } : url}
                ref={x => {this.WebView = x}}
                onMessage={e => onMessage(JSON.parse(e.nativeEvent.data))}
            />
        )
    }
}

Please keep this open. It is very real.

+1 getting the same error.

It’s because the page you’re visiting is overriding postMessage, which may be due to a shim. Showing an error was chosen over invoking the shim’s value of postMessage when you call the value of postMessage that we set.

I thought I’d made it easier to workaround this problem, but it looks like that won’t work. The best you can do at the moment, which is an awful hack, is,

injectedJavaScript="window.postMessage = String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');"

Or just fork the project and remove the code you pointed to. Whatever’s easiest for you!

Ping @satya164 for input on how to resolve.

Hi there! This issue is being closed because it has been inactive for a while. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. Either way, we’re automatically closing issues after a period of inactivity. Please do not take it personally!

If you think this issue should definitely remain open, please let us know. The following information is helpful when it comes to determining if the issue should be re-opened:

  • Does the issue still reproduce on the latest release candidate? Post a comment with the version you tested.
  • If so, is there any information missing from the bug report? Post a comment with all the information required by the issue template.
  • Is there a pull request that addresses this issue? Post a comment with the PR number so we can follow up.

If you would like to work on a patch to fix the issue, contributions are very welcome! Read through the contribution guide, and feel free to hop into #react-native if you need help planning your contribution.

I found workaround that works for me (RN 0.52)

Just add at the end of the injectedScript

window.postMessage = window.originalPostMessage || window.postMessage;
const injectedScript = () => {
  window.postMessage(document.body.innerHTML);
  window.postMessage = window.originalPostMessage || window.postMessage;
}

<WebView
  source={source}
  injectedJavaScript={`(${String(injectedScript)})()`}
  onMessage={this._onMessage}
/>

You could wait for the onLoad before passing onMessage into the component (passing null until the onLoad triggers).

It worked for us, but we had to implement a simple queue in the webview that collects posts until the onLoad has triggered, which then triggers another onLoad inside the webview using injectJavaScript

  onLoad(e) {
    // onload is called multiple times...
    if ( this.state.loaded ) {
      return
    }
    this.setState({ loaded: true }, () => this.bridge.injectJavaScript('window.onLoad()'))
  }
  onMessage(payload) {
    console.log('got message from web view', payload)
  }
  render() {
    return (
      <WebView
        onLoad={this.onLoad}
        ref={n => this.bridge = n} 
        source={require('../assets/bridge.html')} 
        javaScriptEnabled={true} 
        onMessage={this.state.loaded ? this.onMessage.bind(this) : null} 
      />
    )
  }

For me it helped to use ‘useWebKit’ prop on a WebView.

What @MrLoh said, this issue is very real, please keep it open.

If you feel brave, use a shell script to rename the native postMessage:

sed -i '' '/@"window.originalPostMessage = window.postMessage;"/d' node_modules/react-native/React/Views/RCTWebView.m
sed -i '' 's/  "window.postMessage = function(data) {/@"window.nativePostMessage = function(data) {/' node_modules/react-native/React/Views/RCTWebView.m
sed -i '' '/"window.originalPostMessage = window.postMessage," +/d' node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
sed -i '' 's/"window.postMessage = function(data) {" +/"window.nativePostMessage = function(data) {" +/' node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java

After running this script (you will need to do it after each yarn npm install), you can simply use:

window.nativePostMessage('it works')

inside your webView. IMO, this should be done a long time ago… there is a PR here for basically the same thing: https://github.com/facebook/react-native/pull/12997/files?diff=split

import React, { Component } from 'react';
import { View, Text, WebView, Dimensions, Alert } from 'react-native';

const patchPostMessageFunction = function() {
  var originalPostMessage = window.postMessage;
  var patchedPostMessage = function(message, targetOrigin, transfer) {
    originalPostMessage(message, targetOrigin, transfer);
  };
  patchedPostMessage.toString = function() {
    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
  };
  window.postMessage = patchedPostMessage;
};

const injectScript = '(' + String(patchPostMessageFunction) + ')();';

export default class WebviewCustom extends Component {
  constructor(props) {
    super(props);
    this.state = { webviewLoaded: false };
  }

  _onLoadEnd() {
    this.handleNavigationChange();
  }

  handleNavigationChange() {
    const script = 'window.postMessage(document.documentElement.innerHTML)';
    thisWebView && thisWebView.injectJavaScript(script);
  }

  _onMessage(e) {
    Alert.alert(e.nativeEvent.data);
  }

  render() {
    return (
      <View style={{ flex: 1, backgroundColor: 'rgb(255, 255, 255)' }}>
        <WebView
          ref={webview => {
            thisWebView = webview;
          }}
          onLoadEnd={this._onLoadEnd.bind(this)}
          injectedJavaScript={injectScript}
          source={{ uri: this.props.url }}
          onMessage={this._onMessage}
        />
      </View>
    );
  }
}

+1, I’m getting the same error. When there is a iframe on web, it occurs the same error

Re-opening. Is anyone working on a fix or is there a PR waiting for review? I want to make sure this gets fixed before the issue gets closed due to inactivity.

Absolutely still a problem over here too. Please keep open @hramos

Injecting a patched postMessage method did not work for me (RN 0.55, macOS 10.13, iOS). In my case the error only occurred when I rendered an iframe (React + ReactDOM) inside my WebView.

This issue will not occur is you use “useWebKit” prop of webview, that will use WK webview instead of UI webview in case of IOS. But in case if you don’t want to use WK webview, then below is the sample JS code that you can inject in you Webview with “injectedJavaScript” prop.

  `(function () {
      let originalPostMessage = window.postMessage;
      let patchedPostMessage = function(message, targetOrigin, transfer, ...other) { 
        originalPostMessage(message, targetOrigin, transfer, ...other);
      };

      patchedPostMessage.toString = function() { 
        return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
      };

      window.postMessage = patchedPostMessage;
    })()`

The above code did nothing just patched window.postMessage function, the above issue will not appear.

Hi there, we are migrating all the WebView issues to the new repository https://github.com/react-native-community/react-native-webview. Correct me if I’m wrong but this does not seem to happen with the new WKWebview implementation. If it does, feel free to open an issue directly on the new repository and link that issue to it! Thanks,

Yes we know there are workarounds documented here. But it’s still a bug nevertheless.

Please keep open.

While this proplem is real and it should be open. We worked around this problem using https://github.com/CRAlpha/react-native-wkwebview, but sadly this means we have two different implementations of the code injected for iOS and android. It would be much nicer too have a solution working the same on both platforms with the default WebView

I tried @MrLoh’s Code, but I still get the red error screen:

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  WebView,
  Dimensions,
} from 'react-native';

const patchPostMessageJsCode = `(${String(function() {
  var originalPostMessage = window.postMessage
  var patchedPostMessage = function(message, targetOrigin, transfer) {
    originalPostMessage(message, targetOrigin, transfer)
  }
  patchedPostMessage.toString = function() {
    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')
  }
  window.postMessage = patchedPostMessage
})})();`

class MessageWebView extends React.Component {
  constructor(props) {
    super(props)
    this.postMessage = this.postMessage.bind(this)
  }
  postMessage(action) {
    this.WebView.postMessage(JSON.stringify(action))
  }
  render() {
    const { html, source, url, onMessage, ...props } = this.props
    return (
      <WebView
        {...props}
        javaScriptEnabled
        injectedJavaScript={patchPostMessageJsCode}
        source={source ? source : html ? { html } : url}
        ref={x => {this.WebView = x}}
        onMessage={e => onMessage(JSON.parse(e.nativeEvent.data))}
      />
    )
  }
}

export default class WebViewTest extends Component {

  onLoadStart() {
    console.log('start')
  }

  onLoadEnd() {
    console.log('end')
  }

  onMessage() {
    console.log('message')
  }

  render() {
    return (
      <View style={styles.container}>
        <MessageWebView
          source={{uri: 'https://www.google.de'}}
          style={{marginTop: 20}}
          javaScriptEnabled
          startInLoadingState
          onLoadStart={this.onLoadStart}
          onLoadEnd={this.onLoadEnd}
          style={styles.web}
          onMessage={this.onMessage}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  web: {
    flex: 1,
    width: Dimensions.get('window').width,
  },
});

AppRegistry.registerComponent('WebViewTest', () => WebViewTest);

package.json

	"dependencies": {
		"react": "16.0.0-alpha.12",
		"react-native": "0.46.1"
	},

I have same error, without injecting any javascript to WebView - just setting WebView-properties:

source={require(‘…/lib/html/somefile.html’)} onMessage={() => console.log(‘Hello World’)}

and in my …/lib/html/somefile.html’ I have javascript code doing both window.postMessage() and document.addEventListener(“message”,…

Everything worked fine when I had the HTML in a variable “someHtml”, like: source={{html: someHtml}}

So difference seem to be when changing source to HTML-file instead of HTML-string.

Still for me I had other reasons, not related to this, why I really need to read HTML from file instead of string so it would be great to get this issue fixed.

Oh I see. The JS is injected after postMessage is overwritten. Your best bet is to fork it then, and I’ll see what I can do about getting this fixed! Sorry. 😦

Try injectedJavaScript="window.RNPostMessage = window.postMessage;window.postMessage = String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');"

Then use window.RNPostMessag

Same issue with this versions: “react”: “16.3.2”, “react-native”: “0.55.4”,

Here some example code for sending and receiving messages from react native and web view back and fourth.

native side:

import React from 'react'
import PropTypes from 'prop-types'
import MessageWebView from './MessageWebView'
import { View, TouchableOpacity, Text, WebView } from 'react-native'

export default class MessageTest extends React.Component {
    constructor(props) {
        super(props)
        this.postMessage = this.postMessage.bind(this)
    }
    receivedMessage(action) {
        console.log('received message from web view')
        console.log(action)
    }
    postMessage() {
        console.log('sending message from react native')
        this.webView.postMessage('message from react native')
    }
    render() {
        return (
            <View style={{ flex: 1, marginTop: 22 }}>
                <MessageWebView
                    source={require('./MessageTest.html')}
                    ref={x => {
                        this.webView = x
                    }}
                    onMessage={this.receivedMessage}
                />
                <TouchableOpacity onPress={this.postMessage}>
                    <Text>Send message from react native</Text>
                </TouchableOpacity>
            </View>
        )
    }
}

webview side:

<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        html, body {
            margin: 0;
            font-family: sans-serif;
        }
    </style>
</head>
<body>
    <h2>Hello Web from React Native</h2>
    <button>Send message from web</button>
    <textarea style="height: 50%; width: 100%;" readonly></textarea>
    <script>
        const log = document.querySelector('textarea')
        function logMessage(message) {
            log.append(Date.now() + " | " + message + "\n");
        }
        document.querySelector("button").onclick = function() {
            const message = 'message from web view'
            logMessage(message)
            window.postMessage(JSON.stringify(message))
        }
        document.addEventListener("message", function(event) {
            const message = JSON.parse(event.data)
            logMessage(message)
        }, false)
    </script>
</body>
</html>

In my project this red screen doesn’t appear for non-debug build and the web page I’m using still works without any issue. While in debug build I could just dismiss the red screen and continue. But It’s kinda annoying and who knows when an issue’s gonna popping up in non-debug build. Please fix.

+1,encounter this problem

+1, still has this problem

For anyone still struggling with this, I released rn-webview on NPM to solve this exact problem (open-sourced on GitHub). My team and myself are unable or unwilling to eject from our Expo implementation in order to use the community edition linked a few posts above. If you are able to do so, I’d recommend that option. If you want to maintain a purely JavaScript, out-of-the-box Expo experience, the rn-webview package is for you.

I wrote a walkthrough for how it works on Medium. Of course the implementation has to have drawbacks, and the way it works may not be acceptable for your use case. The biggest deal is that, instead of relying on native window.postMessage function, it instead manipulates the history (history.pushState) so that it can listen to the navigation state change event (which is supported by both Android and iOS). Feel free to suggest better implementations in the Issues for the repo or fork and adjust not altering the navigation state is crucial to your project.

Hope this helps!

#12997 will not necessarily fix this. I fixed it locally and submitted a pull request #16502 I also created a gist for a quick fix for react native 0.44.2 here

still not working, on rn - 0.48.4 stills shows a red screen error

@kyle-ilantzis Yeah. Now I have huge problem because docs cause red screen and your solution helps with the red screen but block webview.postMessage - no idea what to do 😉

Is this by any chance solved in a newer version of react-native ? Or is this just laying out here for about half year?

@skrobek Oh no 😱

My objective was to have the JS code in the webview talk to react-native. It was not to have react-native talk to itself through a webview.

patchPostMessage achieved my objective, no more red screen on ios when setting the onMessage property of a webview. JS code in an IOS webview could then talk to react-native