diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 548a6a2..5ad2e29 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -35,4 +35,17 @@ ### 0.2.3 (Oct 9, 2013) * [CB-4915] Incremented plugin version on dev branch. -* [CB-4926] Fixes inappbrowser plugin loading for windows8 \ No newline at end of file +* [CB-4926] Fixes inappbrowser plugin loading for windows8 + +### 0.2.4 (Oct 28, 2013) +* CB-5128: added repo + issue tag to plugin.xml for inappbrowser plugin +* CB-4995 Fix crash when WebView is quickly opened then closed. +* CB-4930 - iOS - InAppBrowser should take into account the status bar +* [CB-5010] Incremented plugin version on dev branch. +* [CB-5010] Updated version and RELEASENOTES.md for release 0.2.3 +* CB-4858 - Run IAB methods on the UI thread. +* CB-4858 Convert relative URLs to absolute URLs in JS +* CB-3747 Fix back button having different dismiss logic from the close button. +* CB-5021 Expose closeDialog() as a public function and make it safe to call multiple times. +* CB-5021 Make it safe to call close() multiple times +>>>>>>> dev diff --git a/plugin.xml b/plugin.xml index 03cb490..94cfb82 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,12 +2,18 @@ + version="0.2.4"> + InAppBrowser Cordova InAppBrowser Plugin Apache 2.0 cordova,in,app,browser,inappbrowser + https://git-wip-us.apache.org/repos/asf/cordova-plugin-inappbrowser.git + https://issues.apache.org/jira/browse/CB/component/12320641 + + + diff --git a/src/android/InAppBrowser.java b/src/android/InAppBrowser.java index 3be0316..da6b241 100644 --- a/src/android/InAppBrowser.java +++ b/src/android/InAppBrowser.java @@ -18,20 +18,6 @@ */ package org.apache.cordova.inappbrowser; -import java.util.HashMap; -import java.util.StringTokenizer; - - -import org.apache.cordova.Config; -import org.apache.cordova.CordovaWebView; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.LOG; -import org.apache.cordova.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; @@ -52,11 +38,7 @@ import android.view.WindowManager.LayoutParams; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; -import android.webkit.WebChromeClient; -import android.webkit.GeolocationPermissions.Callback; -import android.webkit.JsPromptResult; import android.webkit.WebSettings; -import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; @@ -64,6 +46,19 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.Config; +import org.apache.cordova.CordovaArgs; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.LOG; +import org.apache.cordova.PluginResult; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.StringTokenizer; + @SuppressLint("SetJavaScriptEnabled") public class InAppBrowser extends CordovaPlugin { @@ -100,120 +95,134 @@ public class InAppBrowser extends CordovaPlugin { * @param callbackId The callback id used when calling back into JavaScript. * @return A PluginResult object with a status and message. */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - try { - if (action.equals("open")) { - this.callbackContext = callbackContext; - String url = args.getString(0); - String target = args.optString(1); - if (target == null || target.equals("") || target.equals(NULL)) { - target = SELF; - } - HashMap features = parseFeature(args.optString(2)); - - Log.d(LOG_TAG, "target = " + target); - - url = updateUrl(url); - String result = ""; - - // SELF - if (SELF.equals(target)) { - Log.d(LOG_TAG, "in self"); - // load in webview - if (url.startsWith("file://") || url.startsWith("javascript:") - || Config.isUrlWhiteListed(url)) { - this.webView.loadUrl(url); - } - //Load the dialer - else if (url.startsWith(WebView.SCHEME_TEL)) - { - try { - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - this.cordova.getActivity().startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); + public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { + if (action.equals("open")) { + this.callbackContext = callbackContext; + final String url = args.getString(0); + String t = args.optString(1); + if (t == null || t.equals("") || t.equals(NULL)) { + t = SELF; + } + final String target = t; + final HashMap features = parseFeature(args.optString(2)); + + Log.d(LOG_TAG, "target = " + target); + + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + String result = ""; + // SELF + if (SELF.equals(target)) { + Log.d(LOG_TAG, "in self"); + // load in webview + if (url.startsWith("file://") || url.startsWith("javascript:") + || Config.isUrlWhiteListed(url)) { + webView.loadUrl(url); + } + //Load the dialer + else if (url.startsWith(WebView.SCHEME_TEL)) + { + try { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + cordova.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); + } + } + // load in InAppBrowser + else { + result = showWebPage(url, features); } } - // load in InAppBrowser + // SYSTEM + else if (SYSTEM.equals(target)) { + Log.d(LOG_TAG, "in system"); + result = openExternal(url); + } + // BLANK - or anything else else { - result = this.showWebPage(url, features); + Log.d(LOG_TAG, "in blank"); + result = showWebPage(url, features); } + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); } - // SYSTEM - else if (SYSTEM.equals(target)) { - Log.d(LOG_TAG, "in system"); - result = this.openExternal(url); + }); + } + else if (action.equals("close")) { + closeDialog(); + } + else if (action.equals("injectScriptCode")) { + String jsWrapper = null; + if (args.getBoolean(1)) { + jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId()); + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("injectScriptFile")) { + String jsWrapper; + if (args.getBoolean(1)) { + jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId()); + } else { + jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("injectStyleCode")) { + String jsWrapper; + if (args.getBoolean(1)) { + jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); + } else { + jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("injectStyleFile")) { + String jsWrapper; + if (args.getBoolean(1)) { + jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); + } else { + jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("show")) { + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + dialog.show(); } - // BLANK - or anything else - else { - Log.d(LOG_TAG, "in blank"); - result = this.showWebPage(url, features); - } - - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); - pluginResult.setKeepCallback(true); - this.callbackContext.sendPluginResult(pluginResult); - } - else if (action.equals("close")) { - closeDialog(); - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); - } - else if (action.equals("injectScriptCode")) { - String jsWrapper = null; - if (args.getBoolean(1)) { - jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId()); - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("injectScriptFile")) { - String jsWrapper; - if (args.getBoolean(1)) { - jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId()); - } else { - jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("injectStyleCode")) { - String jsWrapper; - if (args.getBoolean(1)) { - jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); - } else { - jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("injectStyleFile")) { - String jsWrapper; - if (args.getBoolean(1)) { - jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); - } else { - jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; - } - injectDeferredObject(args.getString(0), jsWrapper); - } - else if (action.equals("show")) { - Runnable runnable = new Runnable() { - @Override - public void run() { - dialog.show(); - } - }; - this.cordova.getActivity().runOnUiThread(runnable); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); - pluginResult.setKeepCallback(true); - this.callbackContext.sendPluginResult(pluginResult); - } - else { - return false; - } - } catch (JSONException e) { - this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + }); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); + pluginResult.setKeepCallback(true); + this.callbackContext.sendPluginResult(pluginResult); + } + else { + return false; } return true; } + /** + * Called when the view navigates. + */ + @Override + public void onReset() { + closeDialog(); + } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void onDestroy() { + closeDialog(); + } + /** * Inject an object (script or style) into the InAppBrowser WebView. * @@ -241,8 +250,14 @@ public class InAppBrowser extends CordovaPlugin { } else { scriptToInject = source; } + final String finalScriptToInject = scriptToInject; // This action will have the side-effect of blurring the currently focused element - this.inAppWebView.loadUrl("javascript:" + scriptToInject); + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + inAppWebView.loadUrl("javascript:" + finalScriptToInject); + } + }); } /** @@ -274,20 +289,6 @@ public class InAppBrowser extends CordovaPlugin { } } - /** - * Convert relative URL to full path - * - * @param url - * @return - */ - private String updateUrl(String url) { - Uri newUrl = Uri.parse(url); - if (newUrl.isRelative()) { - url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url; - } - return url; - } - /** * Display a new browser with the specified URL. * @@ -311,30 +312,30 @@ public class InAppBrowser extends CordovaPlugin { /** * Closes the dialog */ - private void closeDialog() { - try { - final WebView childView = this.inAppWebView; - Runnable runnable = new Runnable() { - - @Override - public void run() { - childView.loadUrl("about:blank"); + public void closeDialog() { + final WebView childView = this.inAppWebView; + // The JS protects against multiple calls, so this should happen only when + // closeDialog() is called by other native code. + if (childView == null) { + return; + } + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + childView.loadUrl("about:blank"); + if (dialog != null) { + dialog.dismiss(); } - - }; - - this.cordova.getActivity().runOnUiThread(runnable); + } + }); + try { JSONObject obj = new JSONObject(); obj.put("type", EXIT_EVENT); - sendUpdate(obj, false); } catch (JSONException ex) { Log.d(LOG_TAG, "Should never happen"); } - if (dialog != null) { - dialog.dismiss(); - } } /** @@ -438,14 +439,7 @@ public class InAppBrowser extends CordovaPlugin { dialog.setCancelable(true); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { public void onDismiss(DialogInterface dialog) { - try { - JSONObject obj = new JSONObject(); - obj.put("type", EXIT_EVENT); - - sendUpdate(obj, false); - } catch (JSONException e) { - Log.d(LOG_TAG, "Should never happen"); - } + closeDialog(); } }); @@ -620,10 +614,16 @@ public class InAppBrowser extends CordovaPlugin { * * @param obj a JSONObject contain event payload information * @param status the status code to return to the JavaScript environment - */ private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { - PluginResult result = new PluginResult(status, obj); - result.setKeepCallback(keepCallback); - this.callbackContext.sendPluginResult(result); + */ + private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { + if (callbackContext != null) { + PluginResult result = new PluginResult(status, obj); + result.setKeepCallback(keepCallback); + callbackContext.sendPluginResult(result); + if (!keepCallback) { + callbackContext = null; + } + } } diff --git a/src/ios/CDVInAppBrowser.m b/src/ios/CDVInAppBrowser.m index 4170dd2..4ef051f 100644 --- a/src/ios/CDVInAppBrowser.m +++ b/src/ios/CDVInAppBrowser.m @@ -32,6 +32,11 @@ #pragma mark CDVInAppBrowser +@interface CDVInAppBrowser () { + UIStatusBarStyle _previousStatusBarStyle; +} +@end + @implementation CDVInAppBrowser - (CDVInAppBrowser*)initWithWebView:(UIWebView*)theWebView @@ -51,12 +56,8 @@ - (void)close:(CDVInvokedUrlCommand*)command { - if (self.inAppBrowserViewController != nil) { - [self.inAppBrowserViewController close]; - self.inAppBrowserViewController = nil; - } - - self.callbackId = nil; + // Things are cleaned up in browserExit. + [self.inAppBrowserViewController close]; } - (BOOL) isSystemUrl:(NSURL*)url @@ -115,6 +116,7 @@ } } + _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options]; [self.inAppBrowserViewController showLocationBar:browserOptions.location]; @@ -155,8 +157,13 @@ } if (! browserOptions.hidden) { + + UINavigationController* nav = [[UINavigationController alloc] + initWithRootViewController:self.inAppBrowserViewController]; + nav.navigationBarHidden = YES; + if (self.viewController.modalViewController != self.inAppBrowserViewController) { - [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; + [self.viewController presentModalViewController:nav animated:YES]; } } [self.inAppBrowserViewController navigateTo:url]; @@ -166,7 +173,13 @@ { if ([self.inAppBrowserViewController isViewLoaded] && self.inAppBrowserViewController.view.window) return; - [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; + + _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; + + UINavigationController* nav = [[UINavigationController alloc] + initWithRootViewController:self.inAppBrowserViewController]; + nav.navigationBarHidden = YES; + [self.viewController presentModalViewController:nav animated:YES]; } - (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options @@ -355,13 +368,18 @@ if (self.callbackId != nil) { CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"type":@"exit"}]; - [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + self.callbackId = nil; } + // Set navigationDelegate to nil to ensure no callbacks are received from it. + self.inAppBrowserViewController.navigationDelegate = nil; // Don't recycle the ViewController since it may be consuming a lot of memory. // Also - this is required for the PDF/User-Agent bug work-around. self.inAppBrowserViewController = nil; + + if (IsAtLeastiOSVersion(@"7.0")) { + [[UIApplication sharedApplication] setStatusBarStyle:_previousStatusBarStyle]; + } } @end @@ -632,6 +650,11 @@ [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; [super viewDidUnload]; } + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return UIStatusBarStyleDefault; +} - (void)close { @@ -674,6 +697,15 @@ { [self.webView goForward]; } + +- (void)viewWillAppear:(BOOL)animated +{ + if (IsAtLeastiOSVersion(@"7.0")) { + [[UIApplication sharedApplication] setStatusBarStyle:[self preferredStatusBarStyle]]; + } + + [super viewWillAppear:animated]; +} #pragma mark UIWebViewDelegate diff --git a/www/InAppBrowser.js b/www/InAppBrowser.js index 5da53fd..3fe9261 100644 --- a/www/InAppBrowser.js +++ b/www/InAppBrowser.js @@ -22,6 +22,7 @@ var exec = require('cordova/exec'); var channel = require('cordova/channel'); var modulemapper = require('cordova/modulemapper'); +var urlutil = require('cordova/urlutil'); function InAppBrowser() { this.channels = { @@ -30,6 +31,7 @@ function InAppBrowser() { 'loaderror' : channel.create('loaderror'), 'exit' : channel.create('exit') }; + this._alive = true; } InAppBrowser.prototype = { @@ -39,7 +41,10 @@ InAppBrowser.prototype = { } }, close: function (eventname) { - exec(null, null, "InAppBrowser", "close", []); + if (this._alive) { + this._alive = false; + exec(null, null, "InAppBrowser", "close", []); + } }, show: function (eventname) { exec(null, null, "InAppBrowser", "show", []); @@ -77,17 +82,18 @@ InAppBrowser.prototype = { }; module.exports = function(strUrl, strWindowName, strWindowFeatures) { - var iab = new InAppBrowser(); - var cb = function(eventname) { - iab._eventHandler(eventname); - }; - // Don't catch calls that write to existing frames (e.g. named iframes). if (window.frames && window.frames[strWindowName]) { var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open'); return origOpenFunc.apply(window, arguments); } + strUrl = urlutil.makeAbsolute(strUrl); + var iab = new InAppBrowser(); + var cb = function(eventname) { + iab._eventHandler(eventname); + }; + exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); return iab; };