I usually use the HTML5 PostMessage API to communicate information from my iframed content to the parent frame. Recently I\'ve had my content used inside an Android WebView (as
I realize this question is old but I ran into it so I figured I would answer here. In short - I am finding that postMessage does work at least for communication from a child iframe to a parent window BUT...
Turns out we really didn't like the way the iframe behaved in android's WebView so we rendered the contents of the iframe directly instead (as you suggest). This left us with two problems - first we had lots of messaging hooks from that iframe to it's parent and second we still needed to call out to android to react to these events.
Here's an example message from our code - which was sprinkled throughout the iframe:
parent.postMessage(JSON.stringify({
action : 'openModal',
source : embedId
}), '*');
When we're on Android what we want is to use android's support for javascript interfaces to inject an object to handle this request when running in a WebView.
On the Android side this will look something like this:
class JsObject {
@JavascriptInterface
public boolean postMessage(String json, String transferList) {
return false; // here we return true if we handled the post.
}
}
// And when initializing the webview...
webView.addJavascriptInterface(new JsObject(), "totDevice");
Now when running inside this WebView totDevice
will exist and when running in an iframe it won't. So now we can create a wrapper to check for this condition and cleanly switch between the two methods rather than calling parent.postMessage
directly. Here we also added a boolean switch in our Android implementation in case you only wanted to handle some of the messages:
function postMessage(parent, json, transferlist) {
if (!totDevice || !totDevice.postMessage(json, transferList)) {
parent.postMessage(json, transferlist);
}
}
Our original postMessage from above can be rewritten:
postMessage(parent, JSON.stringify({
action : 'openModal',
source : embedId
}), '*');
Now we have a single set of code that can run in an iframe or android WebView with no change (at least to this part of the code).
I hope that helps someone.
You need to use an intermediate abstract interface that in one implementation processes messages via PostMessage and in another case via addJavascriptInterface.
window.addEventListener("message", onReceivedPostMessage, false);
function onReceivedPostMessage(event){
//..ex deconstruct event into action & params
var action = event.data.action;
var params = event.data.params;
performAction(action, params); //performAction would be the uniform API
}
function onReceivedActivityMessageViaJavascriptInterface(json){
//..ex deconstruct data into action & params
var data = JSON.parse(json);
var action = data.action;
var params = data.params;
performAction(action, params); //performAction would be the uniform API
}
Recently we had to develop a project that needed to communicate our native Android app with an external webview integration from a third party.
The idea that this question raises in stackoverflow is very interesting, more so if you can't touch the JS code of that webview.
I describe what we did to be able to communicate the webview with the native app through the message steps via the JS PostMessage API.
Using our webview implementation. We implemented the onPageFinished method in order to inject our JS code to load the web.
override fun onPageFinished(url: String?) {
webview.loadUrl(
"javascript:(function() {" +
"window.parent.addEventListener ('message', function(event) {" +
" Android.receiveMessage(JSON.stringify(event.data));});" +
"})()"
)
}
Basically what we are doing is creating a listener that sends those messages to our own JS and Android Bridge interface. Which we've previously created in the webview setup in our Android activity as we normally do with addJavascriptInterface
webview.addJavascriptInterface(JsObject(presenter), "Android”)
This way, we already have that communication bridge and all the messages sent by the postMessage will reach us in that interface that is subscribed with that listener.
class JsObject(val presenter: Presenter) {
@JavascriptInterface
fun receiveMessage(data: String): Boolean {
presenter.onDataReceived(data)
Log.d("Data from JS", data)
return true
}
I was facing similar issue, I wanted to listen to .postMessage
event occuring in webview. I checked in raw html that the event was fired like this
window.parent.postMessage(JSON.stringify(message), '*');
Hence I dug more into postMessage and found this link for adding eventListener
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
if (event.origin !== "http://example.org:8080")
return;
// ...
}
durban's answer helped me for setting up Android JS Interface.
Firstly a custom class for JavascriptInterface like this
class JsObject {
@JavascriptInterface
public void receiveMessage(String data) {
Log.i("JsObject", "postMessage data="+data);
//handle data here
}
}
Add javascriptInterface to webview before loading url/html
webView.addJavascriptInterface(new JsObject(), "Android"); //"Android" is just a name
Then call javascript in the onPageStarted
callback of WebViewClient
like below
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
webView.loadUrl("javascript:(function() {" +
"function receiveMessage(event) {\n" +
"Android.receiveMessage(JSON.stringify(event.data));\n" +
"}" +
"window.addEventListener(\"message\", receiveMessage, false);"+
"})()"
);
Log.i(TAG, "onPageStarted "+url);
}