Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react native html postMessage can not reach to WebView #11594

Closed
peixin opened this issue Dec 22, 2016 · 47 comments
Closed

react native html postMessage can not reach to WebView #11594

peixin opened this issue Dec 22, 2016 · 47 comments
Labels
Component: WebView Related to the WebView component. Resolution: Locked This issue was locked by the bot.

Comments

@peixin
Copy link

peixin commented Dec 22, 2016

I use React Native webview to show index.html, and HTML will post messge to the app. The app will then receive the message and write it to console. The problem is the app cannot receive messages, when postMessage is immediately run on head. I think it maybe related to HTML not finished loading. I then used a delay with setTimeout, and it worked.

Now I want to know:

  • Is there better way to solve this problem?
  • Why the delay 100 milliscond did not work, but delay 200 milliscond did?

I am using React Native version 0.39.0, and Node version 7.2.0.

Here is the code I have so far:

index.html

<head>
<title>Index</title>
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript">
    // can not be received
    postMessage('send to react native from index inline, no delay', '*');

    // can not be received
    setTimeout(function(){
        postMessage('send to react native from index inline, delay 0 milliscond', '*')
    }, 0);

    // can received
    setTimeout(function(){
        postMessage('send to react native from index inline, delay 100 milliscond', '*')
    }, 100);
    
    onload = function() {
        // can not be received
        postMessage('send to react native from index inline after onload, no delay', '*')

        // can received
        setTimeout(function () {
            postMessage('send to react native from index inline after onload, delay 0 milliscond', '*')
        }, 0);
    };
</script>

index.js

// can not be received
postMessage('send to react native from index.js, no delay', '*');

// can not be received
setTimeout(function() {
    postMessage('send to react native from index.js, delay 100 milliscond', '*')
}, 100);

// can received
setTimeout(function() {
    postMessage('send to react native from index.js, delay 200 milliscond', '*')
}, 200);

React Native web_view_page.js

return (
        <WebView
            source={{uri: 'http://127.0.0.1:8000/index.html'}}
            onMessage={(event) => console.log('onMessage:', event.nativeEvent.data)}/>
    );

Chrome console log

2016-12-21 11:45:02.367 web_view.js:147 onMessage: send to react native from index inline after onload, delay 0 milliscond
2016-12-21 11:45:02.491 web_view.js:147 onMessage: send to react native from index inline, delay 100 milliscond
2016-12-21 11:45:02.628 web_view.js:147 onMessage: send to react native from index.js, delay 200 milliscond
@anttu
Copy link

anttu commented Jan 8, 2017

Without setTimeout() I'm getting:

Failed to execute 'postMessage' on 'Window': 2 arguments required, but only 1 present.

However it does seem to work with the delay.

@farazs
Copy link

farazs commented Jan 11, 2017

Having the same issue. If a message is sent right when the page is loaded and javascript is executed it fails with that error. However, if it's executed later it seems to work. Any update on this?

@Dryymoon
Copy link

Dryymoon commented Jan 24, 2017

You can wait for RN postMessage bridge ready, by running this function once before any window.postMessage call in yours code, tested on RN 0.39

function awaitPostMessage() {
  let isReactNativePostMessageReady = !!window.originalPostMessage;
  const queue = [];
  let currentPostMessageFn = function store(message) {
    if (queue.length > 100) queue.shift();
    queue.push(message);
  };
  if (!isReactNativePostMessageReady) {
    // const originalPostMessage = window.postMessage;
    Object.defineProperty(window, 'postMessage', {
      configurable: true,
      enumerable: true,
      get() {
        return currentPostMessageFn;
      },
      set(fn) {
        currentPostMessageFn = fn;
        isReactNativePostMessageReady = true;
        setTimeout(sendQueue, 0);
      }
    });
  }

  function sendQueue() {
    while (queue.length > 0) window.postMessage(queue.shift());
  }
}

Usage:

awaitPostMessage(); // Call this only once in your Web Code.

/* After you can call window.postMessage many times, and as soon as RN Message Bridge is ready - messadges delivered */
window.postMessage('Once :) ');
window.postMessage('Twice :) ');

Notice: In my code i limit queue for 100 message, fell free to increase this, or remove limit at all.

@sylvainbaronnet
Copy link

Thanks @Dryymoon, Do you know if it works in Android too ?

@artdevgame
Copy link

@Dryymoon Thanks for your solution. Do you know if this is intended behaviour? Seems a little odd to have to wait on the bridge to be ready to use before calling it.

I would expect the queueing behaviour to be taken care of automatically by react-native.

@jqn
Copy link

jqn commented Jan 31, 2017

@beiming In my case I wanted to trigger postMessage on click and I wasn't able to trigger the message until I added event.perventDefault() in my click event like this in the WebView page:

  $(".my-btn").on("click", function(event){
    event.preventDefault();
    postMessage("send message");
  });

Also I'm able to trigger postMessage by wrapping it with:

$(document).ready(function(){
   postMessage("ready");
});

@gp3gp3gp3
Copy link

@beiming Thanks for this workaround. Setting the 0 millisecond timeout on window.onload works in iOS but doesn't seem to work on android unless I set it to 200.

This still feels flimsy though, as I'm banking on I'm guessing the native bridge to be good to go at 200 milliseconds? I don't really know why this behavior is happening, but setting timeouts doesn't feel right.

@andreibarabas
Copy link

guys, this works on Android (i haven't tested it on iOS yet).

  <html>
   <body>
   <script>

   function waitForBridge() {
      
       //the react native postMessage has only 1 parameter
       //while the default one has 2, so check the signature
       //of the function
       
       if (window.postMessage.length !== 1){
         
         setTimeout(waitForBridge, 200);
       }
       else {
         
         window.postMessage('abc');
       }
    }
    
    window.onload = waitForBridge;
    </script>
    </body>
    </html>

@farazs
Copy link

farazs commented Mar 1, 2017

I think for Android you can use window.__REACT_WEB_VIEW_BRIDGE.postMessage instead.

the delay for Android is in executing this:

window.postMessage = function(data) {
  __REACT_WEB_VIEW_BRIDGE.postMessage(String(data));
};

You can use the javascript interface directly instead for Android based on the implementation. However, this could break in a future version if they update it.

@doxiaodong
Copy link

doxiaodong commented Mar 15, 2017

@Dryymoon' s method is good, but in some env, you'd better use es5 code:

    function awaitPostMessage() {
      var isReactNativePostMessageReady = false;
      var queue = [];
      var currentPostMessageFn = function store(message) {
        queue.push(message);
      };
      if (!isReactNativePostMessageReady) {
        Object.defineProperty(window, "postMessage", {
          configurable: true,
          enumerable: true,
          get() {
            return currentPostMessageFn;
          },
          set(fn) {
            currentPostMessageFn = fn;
            isReactNativePostMessageReady = true;
            setTimeout(sendQueue, 0);
          }
        });
      }

      function sendQueue() {
        while (queue.length > 0) window.postMessage(queue.shift());
      }
    }
    awaitPostMessage();

@farazs
Copy link

farazs commented Apr 11, 2017

@Dryymoon I'm curious why the setTimeout is needed? Once the set is called we update the postMessage and it should work right? It seems not to work without it but I'm not sure why.

@kyle-ilantzis
Copy link

For Android, Dryymoon's solution works when using the debug version. However, the release version causes the error "__REACT_WEB_VIEW_BRIDGE.postMessage is not a function"

@chriscohoat
Copy link

@doxiaodong I think you need to replace the get/set portion with the following:

...
        get: function () {
          return currentPostMessageFn;
        },
        set: function (fn) {
          currentPostMessageFn = fn;
          isReactNativePostMessageReady = true;
          setTimeout(sendQueue, 0);
        }
...

Otherwise Webstorm is telling me that ECMAScript 5.1 doesn't support shorthand generator methods .

@shenlq
Copy link

shenlq commented Apr 27, 2017

For Android, the release version causes the error "__REACT_WEB_VIEW_BRIDGE.postMessage is not a function"!!!

@kyle-ilantzis
Copy link

@shenlq if you disable proguard then it should work again

In android/app/build.gradle

def enableProguardInReleaseBuilds = false

@ashokseervidev
Copy link

ashokseervidev commented May 2, 2017

@Dryymoon

code works but now i am getting below error. any idea?

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

@coproduto
Copy link

I had the same issue here. I managed to "solve it" as my code needs to be ran only when the user changes page, so I can bind it to window.onbeforeunload, but there definitely needs to be a canonical way to check if the WebView Bridge is ready.

@farazs
Copy link

farazs commented May 2, 2017

For Android there's the __REACT_WEB_VIEW_BRIDGE workaround I mentioned.

For iOS I ended up using https://github.com/CRAlpha/react-native-wkwebview and it doesn't have this issue with the bridge.

UIWebView also has a lot of issues that are fixed in WKWebView.

Hope this helps.

@ashokseervidev
Copy link

@pcstl

could you please provide examples?

@joenoon
Copy link
Contributor

joenoon commented May 3, 2017

I ran into "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined" the other day using the awaitPostMessage trick.

What ended up working for me ended up being pretty simple:

  1. Give your js an entrypoint function like main()
  2. Add a ref to your WebView
  3. in componentDidMount: this.webView.injectJavaScript('main()')

@Dryymoon
Copy link

Dryymoon commented May 3, 2017

"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined" - is normal usecase in DEV mode, but in PRODUCTION this check isnt running. Die to code https://github.com/facebook/react-native/blob/master/React/Views/RCTWebView.m#L283

@Dryymoon
Copy link

Dryymoon commented May 3, 2017

Maybe solve problem, but check for native code in RN: https://github.com/facebook/react-native/blob/master/React/Views/RCTWebView.m#L285
is Wrong, because

String(window.postMessage) === 'function () { [native code] }'

and

String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage') === 'function postMessage() { [native code] }'

And it not equal

But
Try THIS code:

function awaitPostMessage() {
  var isReactNativePostMessageReady = !!window.originalPostMessage;
  var queue = [];
  var currentPostMessageFn = function store(message) {
    if (queue.length > 100) queue.shift();
    queue.push(message);
  };
  if (!isReactNativePostMessageReady) {
    var originalPostMessage = window.postMessage;
    Object.defineProperty(window, 'postMessage', {
      configurable: true,
      enumerable: true,
      get: function () {
        return currentPostMessageFn;
      },
      set: function (fn) {
        currentPostMessageFn = fn;
        isReactNativePostMessageReady = true;
        setTimeout(sendQueue, 0);
      }
    });
    window.postMessage.toString = function () {
      return String(originalPostMessage);
    };
  }

  function sendQueue() {
    while (queue.length > 0) window.postMessage(queue.shift());
  }
}

Usage:

awaitPostMessage(); // Call this only once in your Web Code.

/* After you can call window.postMessage many times, and as soon as RN Message Bridge is ready - messages delivered */
window.postMessage('Once :) ');
window.postMessage('Twice :) ');

Notice: In my code i limit queue for 100 message, fell free to increase this, or remove limit at all.

@Noitidart
Copy link

Noitidart commented May 28, 2017

The solution @andreibarabas tested on Android works great for me on iOS.

How come we are going to so many extremes and not just using the postMessage.length method as @andreibarabas did? This one is so simple.

I load my webview with this:

<script>
    function whenRNPostMessageReady(cb) {
        if (postMessage.length === 1) cb();
        else setTimeout(function() { whenRNPostMessageReady(cb) }, 100);
    }
</script>

And usage of it:

<script>
    function whenRNPostMessageReady(cb) {
        if (postMessage.length === 1) cb();
        else setTimeout(function() { whenRNPostMessageReady(cb) }, 100);
    }


    whenRNPostMessageReady(function() {
        postMessage('hi react native!!!');
    });
</script>

@peixin peixin closed this as completed Jun 6, 2017
@farazs
Copy link

farazs commented Jun 6, 2017

Why was this closed? It hasn't been resolved and the current implementation is flawed requiring complex workarounds.

@ghost
Copy link

ghost commented Jul 18, 2017

Just a quick note, @Dryymoon this code fails in Safari 9 :/

It generates an

Attempting to change configurable attribute of unconfigurable property.

error, any thoughts on fixing?

@jjzazuet
Copy link

Also, just to reiterate.

Placing comments inside the injectedJavaScript property of WebView breaks bridge communication on Android.

So don't do that I guess :P.

@Noitidart
Copy link

Noitidart commented Nov 19, 2017

I verify @jjzazuet comment - comments work on iOS but not on Android - thanks for that

@nirpeled
Copy link

Can anyone please explain me why is it okay to do these "hacks" instead of just fixing RN's WebView so it won't override the global postMessage in a way that it's breaking the API? What am I missing here? Thanks!

@andrew09
Copy link

I used @Dryymoon solution and it has worked really well. Experiencing some issues in ios9 but I am working on supporting that now. I will post an update with an updated solution.

@vvavepacket
Copy link

guys, do you have any idea why a webview post message back and forth in a release build is super slow yet fast in a debug build?

@khanhtdbse
Copy link

@Dryymoon
This method still works. Thank a lot.
But there a lot of tricky here 😢

@mattbachman31
Copy link

@jqn We also ran into that issue on our app, and the fix that is in RN 0.53-rc here fixes issues on page load, but not on form submit. I filed a separate bug for this issue here if anyone else is dealing with that issue

@willhlaw
Copy link

I second @vvavepacket question. Why does webview post message seem much slower in production than in a debug build (at least in expo comparing development mode (~0.5s) vs production mode (>5s)?

@Karasynth
Copy link

@willhlaw @vvavepacket I am experience the same issue in production build. Any work around or update on this?

I am using react-native 0.54.2 and react-native-wkwebview-reborn 1.16.0

@YusufHussein
Copy link

apparently injected javascript code doesn’t work in android unless compressed (you can use this online tool http://javascriptcompressor.com/ )

@Noitidart
Copy link

@YusufHussein it doesn't have to be compressed. You just cannot use any comments.

@YusufHussein
Copy link

@Noitidart well I tried it without comments but it didn't work untill I compressed the code
I am using react native 0.54.2 and Android simulator 6.0 Marshmallow

@stale
Copy link

stale bot commented Aug 20, 2018

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Aug 20, 2018
@kirillpisarev
Copy link

Still no luck posting a message on document ready within a WebView page without a dirty hack, suggested above. Is any hope that the problem will be solved without this mess, cuz it exists for about 2.5 years...

function RNPostMessage(message) {
      if (window.postMessage.length === 1) window.postMessage(message);
      else setTimeout(RNPostMessage.bind(null, message), 100);
}

@stale stale bot removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Aug 21, 2018
@jamonholmgren
Copy link
Collaborator

Migrated issue to react-native-webview/react-native-webview#5. This one can be closed.

@vladikoff
Copy link

Quick note about this fix for Android, if you used postMessage.length === 1 fix for your Android app, Chrome 70 (and maybe 69?) now reports postMessage.length as 1. So your app might be broken.

Use the [native code] fix or something like postMessage.toString().includes('__REACT_WEB_VIEW_BRIDGE')

@facebook facebook locked as resolved and limited conversation to collaborators Nov 6, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Dec 11, 2019
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Component: WebView Related to the WebView component. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests