diff --git a/README.md b/README.md index 503dd83b8..5e2a99337 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ The object returned from a call to `cordova.InAppBrowser.open` when the target i - __loaderror__: event fires when the `InAppBrowser` encounters an error when loading a URL. - __exit__: event fires when the `InAppBrowser` window is closed. - __beforeload__: event fires when the `InAppBrowser` decides whether to load an URL or not (only with option `beforeload=yes`). + - __message__: event fires when the `InAppBrowser` receives a message posted from the page loaded inside the `InAppBrowser` Webview. - __callback__: the function that executes when the event fires. The function is passed an `InAppBrowserEvent` object as a parameter. @@ -238,6 +239,7 @@ function showHelp(url) { inAppBrowserRef.addEventListener('beforeload', beforeloadCallBack); + inAppBrowserRef.addEventListener('message', messageCallBack); } function loadStartCallBack() { @@ -252,6 +254,13 @@ function loadStopCallBack() { inAppBrowserRef.insertCSS({ code: "body{font-size: 25px;" }); + inAppBrowserRef.executeScript({ code: "\ + var message = 'this is the message';\ + var messageObj = {my_message: message};\ + var stringifiedMessageObj = JSON.stringify(messageObj);\ + webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);" + }); + $('#status-message').text(""); inAppBrowserRef.show(); @@ -300,11 +309,15 @@ function beforeloadCallback(params, callback) { } +function messageCallback(params){ + $('#status-message').text("message received: "+params.data.my_message); +} + ``` ### InAppBrowserEvent Properties -- __type__: the eventname, either `loadstart`, `loadstop`, `loaderror`, or `exit`. _(String)_ +- __type__: the eventname, either `loadstart`, `loadstop`, `loaderror`, `message` or `exit`. _(String)_ - __url__: the URL that was loaded. _(String)_ @@ -312,6 +325,8 @@ function beforeloadCallback(params, callback) { - __message__: the error message, only in the case of `loaderror`. _(String)_ +- __data__: the message contents , only in the case of `message`. A stringified JSON object. _(String)_ + ### Supported Platforms @@ -323,7 +338,11 @@ function beforeloadCallback(params, callback) { ### Browser Quirks -`loadstart` and `loaderror` events are not being fired. +`loadstart`, `loaderror`, `message` events are not fired. + +### Windows Quirks + +`message` event is not fired. ### Quick Example @@ -344,6 +363,7 @@ function beforeloadCallback(params, callback) { - __loadstop__: event fires when the `InAppBrowser` finishes loading a URL. - __loaderror__: event fires when the `InAppBrowser` encounters an error loading a URL. - __exit__: event fires when the `InAppBrowser` window is closed. + - __message__: event fires when the `InAppBrowser` receives a message posted from the page loaded inside the `InAppBrowser` Webview. - __callback__: the function to execute when the event fires. The function is passed an `InAppBrowserEvent` object. diff --git a/src/android/InAppBrowser.java b/src/android/InAppBrowser.java index 93d946089..67ebf9eae 100644 --- a/src/android/InAppBrowser.java +++ b/src/android/InAppBrowser.java @@ -48,6 +48,7 @@ Licensed to the Apache Software Foundation (ASF) under one import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.HttpAuthHandler; +import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; @@ -95,6 +96,7 @@ public class InAppBrowser extends CordovaPlugin { private static final String LOAD_START_EVENT = "loadstart"; private static final String LOAD_STOP_EVENT = "loadstop"; private static final String LOAD_ERROR_EVENT = "loaderror"; + private static final String MESSAGE_EVENT = "message"; private static final String CLEAR_ALL_CACHE = "clearcache"; private static final String CLEAR_SESSION_CACHE = "clearsessioncache"; private static final String HARDWARE_BACK_BUTTON = "hardwareback"; @@ -952,8 +954,24 @@ public void openFileChooser(ValueCallback uploadMsg, String acceptType) settings.setBuiltInZoomControls(showZoomControls); settings.setPluginState(android.webkit.WebSettings.PluginState.ON); + // Add postMessage interface + class JsObject { + @JavascriptInterface + public void postMessage(String data) { + try { + JSONObject obj = new JSONObject(); + obj.put("type", MESSAGE_EVENT); + obj.put("data", new JSONObject(data)); + sendUpdate(obj, true); + } catch (JSONException ex) { + LOG.e(LOG_TAG, "data object passed to postMessage has caused a JSON error."); + } + } + } + if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { settings.setMediaPlaybackRequiresUserGesture(mediaPlaybackRequiresUserGesture); + inAppWebView.addJavascriptInterface(new JsObject(), "cordova_iab"); } String overrideUserAgent = preferences.getString("OverrideUserAgent", null); @@ -1270,6 +1288,11 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); + // Set the namespace for postMessage() + if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1){ + injectDeferredObject("window.webkit={messageHandlers:{cordova_iab:cordova_iab}}", null); + } + // CB-10395 InAppBrowser's WebView not storing cookies reliable to local device storage if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().flush(); diff --git a/src/ios/CDVUIInAppBrowser.m b/src/ios/CDVUIInAppBrowser.m index 501987a58..133b6da9f 100644 --- a/src/ios/CDVUIInAppBrowser.m +++ b/src/ios/CDVUIInAppBrowser.m @@ -337,6 +337,16 @@ - (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command [self.inAppBrowserViewController navigateTo:url]; } +-(void)createIframeBridge +{ + // Create an iframe bridge in the new document to communicate with the CDVThemeableBrowserViewController + NSString* jsIframeBridge = @"var e = _cdvIframeBridge=d.getElementById('_cdvIframeBridge'); if(!_cdvIframeBridge) {e = _cdvIframeBridge = d.createElement('iframe'); e.id='_cdvIframeBridge'; e.style.display='none'; d.body.appendChild(e);}"; + // Add the postMessage API + NSString* jspostMessageApi = @"window.webkit={messageHandlers:{cordova_iab:{postMessage:function(message){_cdvIframeBridge.src='gap-iab://message/'+encodeURIComponent(message);}}}}"; + // Inject the JS to the webview + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"(function(d){%@%@})(document)", jsIframeBridge, jspostMessageApi]]; +} + // This is a helper method for the inject{Script|Style}{Code|File} API calls, which // provides a consistent method for injecting JavaScript code into the document. // @@ -348,9 +358,6 @@ - (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command - (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper { - // Ensure an iframe bridge is created to communicate with the CDVUIInAppBrowserViewController - [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){_cdvIframeBridge=d.getElementById('_cdvIframeBridge');if(!_cdvIframeBridge) {var e = _cdvIframeBridge = d.createElement('iframe');e.id='_cdvIframeBridge'; e.style.display='none';d.body.appendChild(e);}})(document)"]; - if (jsWrapper != nil) { NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@[source] options:0 error:nil]; NSString* sourceArrayString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; @@ -472,6 +479,22 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest* } [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; return NO; + }else if ([scriptCallbackId isEqualToString:@"message"] && (self.callbackId != nil)) { + // Send a message event + NSString* scriptResult = [url path]; + if ((scriptResult != nil) && ([scriptResult length] > 1)) { + scriptResult = [scriptResult substringFromIndex:1]; + NSError* __autoreleasing error = nil; + NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if (error == nil) { + NSMutableDictionary* dResult = [NSMutableDictionary new]; + [dResult setValue:@"message" forKey:@"type"]; + [dResult setObject:decodedResult forKey:@"data"]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dResult]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + } + } } } @@ -513,6 +536,7 @@ - (void)webViewDidStartLoad:(UIWebView*)theWebView - (void)webViewDidFinishLoad:(UIWebView*)theWebView { + [self createIframeBridge]; if (self.callbackId != nil) { // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected). NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; diff --git a/src/ios/CDVWKInAppBrowser.m b/src/ios/CDVWKInAppBrowser.m index 6c1b51a9a..89b739124 100644 --- a/src/ios/CDVWKInAppBrowser.m +++ b/src/ios/CDVWKInAppBrowser.m @@ -555,23 +555,37 @@ - (void)userContentController:(nonnull WKUserContentController *)userContentCont CDVPluginResult* pluginResult = nil; - NSDictionary* messageContent = (NSDictionary*) message.body; - NSString* scriptCallbackId = messageContent[@"id"]; - - if([messageContent objectForKey:@"d"]){ - NSString* scriptResult = messageContent[@"d"]; - NSError* __autoreleasing error = nil; - NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; - if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult]; + if([message.body isKindOfClass:[NSDictionary class]]){ + NSDictionary* messageContent = (NSDictionary*) message.body; + NSString* scriptCallbackId = messageContent[@"id"]; + + if([messageContent objectForKey:@"d"]){ + NSString* scriptResult = messageContent[@"d"]; + NSError* __autoreleasing error = nil; + NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION]; + } } else { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; + }else if(self.callbackId != nil){ + // Send a message event + NSString* messageContent = (NSString*) message.body; + NSError* __autoreleasing error = nil; + NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[messageContent dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if (error == nil) { + NSMutableDictionary* dResult = [NSMutableDictionary new]; + [dResult setValue:@"message" forKey:@"type"]; + [dResult setObject:decodedResult forKey:@"data"]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dResult]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; } - } else { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]]; } - - [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; } - (void)didStartProvisionalNavigation:(WKWebView*)theWebView diff --git a/tests/tests.js b/tests/tests.js index 04b6a20cb..1a0619d59 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -24,6 +24,7 @@ var cordova = require('cordova'); var isWindows = cordova.platformId === 'windows'; var isIos = cordova.platformId === 'ios'; +var isAndroid = cordova.platformId === 'android'; var isBrowser = cordova.platformId === 'browser'; window.alert = window.alert || navigator.notification.alert; @@ -151,6 +152,32 @@ exports.defineAutoTests = function () { done(); }); }); + + it('inappbrowser.spec.7 should support message event', function (done) { + if (!isAndroid && !isIos) { + return pending(cordova.platformId + ' platform doesn\'t support message event'); + } + var messageKey = 'my_message'; + var messageValue = 'is_this'; + iabInstance = cordova.InAppBrowser.open(url, '_blank', platformOpts); + iabInstance.addEventListener('message', function (evt) { + // Verify message event + expect(evt).toBeDefined(); + expect(evt.type).toEqual('message'); + expect(evt.data).toBeDefined(); + expect(evt.data[messageKey]).toBeDefined(); + expect(evt.data[messageKey]).toEqual(messageValue); + done(); + }); + iabInstance.addEventListener('loadstop', function (evt) { + var code = '(function(){\n' + + ' var message = {' + messageKey + ': "' + messageValue + '"};\n' + + ' webkit.messageHandlers.cordova_iab.postMessage(JSON.stringify(message));\n' + + '})()'; + iabInstance.executeScript({ code: code }); + }); + + }); }); }; if (isIos) { diff --git a/types/index.d.ts b/types/index.d.ts index 3bac6707b..b2dd0dad4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -6,7 +6,7 @@ // Copyright (c) Microsoft Open Technologies Inc // Licensed under the MIT license. // TypeScript Version: 2.3 -type channel = "loadstart" | "loadstop" | "loaderror" | "exit"; +type channel = "loadstart" | "loadstop" | "loaderror" | "exit" | "message"; interface Window { /** diff --git a/www/inappbrowser.js b/www/inappbrowser.js index 7764765ad..9ef96184f 100644 --- a/www/inappbrowser.js +++ b/www/inappbrowser.js @@ -38,7 +38,8 @@ 'loadstop': channel.create('loadstop'), 'loaderror': channel.create('loaderror'), 'exit': channel.create('exit'), - 'customscheme': channel.create('customscheme') + 'customscheme': channel.create('customscheme'), + 'message': channel.create('message') }; }