From 228703a63b088cbb635aa58f8a1015a9d5d818ab Mon Sep 17 00:00:00 2001 From: wvengen Date: Fri, 13 Jul 2018 22:14:01 +0200 Subject: [PATCH 1/2] CB-14188: add beforeload event, catching navigation before it happens --- README.md | 21 ++++++++++++- src/android/InAppBrowser.java | 58 ++++++++++++++++++++++++++++++----- src/ios/CDVInAppBrowser.h | 8 ++++- src/ios/CDVInAppBrowser.m | 49 +++++++++++++++++++++++++++-- www/inappbrowser.js | 11 ++++++- 5 files changed, 133 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index eade58a37..9aa1a4382 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ instance, or the system browser. Android supports these additional options: - __hidden__: set to `yes` to create the browser and load the page, but not show it. The loadstop event fires when loading is complete. Omit or set to `no` (default) to have the browser open and load normally. + - __beforeload__: set to `yes` to enable the `beforeload` event to modify which pages are actually loaded in the browser. - __clearcache__: set to `yes` to have the browser's cookie cache cleared before the new window is opened - __clearsessioncache__: set to `yes` to have the session cookie cache cleared before the new window is opened - __closebuttoncaption__: set to a string to use as the close button's caption instead of a X. Note that you need to localize this value yourself. @@ -137,6 +138,7 @@ instance, or the system browser. iOS supports these additional options: - __hidden__: set to `yes` to create the browser and load the page, but not show it. The loadstop event fires when loading is complete. Omit or set to `no` (default) to have the browser open and load normally. + - __beforeload__: set to `yes` to enable the `beforeload` event to modify which pages are actually loaded in the browser. - __clearcache__: set to `yes` to have the browser's cookie cache cleared before the new window is opened - __clearsessioncache__: set to `yes` to have the session cookie cache cleared before the new window is opened - __closebuttoncolor__: set as a valid hex color string, for example: `#00ff00`, to change from the default __Done__ button's color. Only applicable if toolbar is not disabled. @@ -217,6 +219,7 @@ The object returned from a call to `cordova.InAppBrowser.open` when the target i - __loadstop__: event fires when the `InAppBrowser` finishes loading a URL. - __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`). - __callback__: the function that executes when the event fires. The function is passed an `InAppBrowserEvent` object as a parameter. @@ -230,7 +233,7 @@ function showHelp(url) { var target = "_blank"; - var options = "location=yes,hidden=yes"; + var options = "location=yes,hidden=yes,beforeload=yes"; inAppBrowserRef = cordova.InAppBrowser.open(url, target, options); @@ -240,6 +243,8 @@ function showHelp(url) { inAppBrowserRef.addEventListener('loaderror', loadErrorCallBack); + inAppBrowserRef.addEventListener('beforeload', beforeloadCallBack); + } function loadStartCallBack() { @@ -288,6 +293,20 @@ function executeScriptCallBack(params) { } +function beforeloadCallback(params, callback) { + + if (params.url.startsWith("http://www.example.com/")) { + + // Load this URL in the inAppBrowser. + callback(params.url); + } else { + + // The callback is not invoked, so the page will not be loaded. + $('#status-message').text("This browser only opens pages on http://www.example.com/"); + } + +} + ``` ### InAppBrowserEvent Properties diff --git a/src/android/InAppBrowser.java b/src/android/InAppBrowser.java index 92ca3c1a1..93d946089 100644 --- a/src/android/InAppBrowser.java +++ b/src/android/InAppBrowser.java @@ -110,6 +110,7 @@ public class InAppBrowser extends CordovaPlugin { private static final String HIDE_URL = "hideurlbar"; private static final String FOOTER = "footer"; private static final String FOOTER_COLOR = "footercolor"; + private static final String BEFORELOAD = "beforeload"; private static final List customizableOptions = Arrays.asList(CLOSE_BUTTON_CAPTION, TOOLBAR_COLOR, NAVIGATION_COLOR, CLOSE_BUTTON_COLOR, FOOTER_COLOR); @@ -138,6 +139,7 @@ public class InAppBrowser extends CordovaPlugin { private boolean hideUrlBar = false; private boolean showFooter = false; private String footerColor = ""; + private boolean useBeforeload = false; private String[] allowedSchemes; /** @@ -246,6 +248,20 @@ else if (SYSTEM.equals(target)) { else if (action.equals("close")) { closeDialog(); } + else if (action.equals("loadAfterBeforeload")) { + if (!useBeforeload) { + LOG.e(LOG_TAG, "unexpected loadAfterBeforeload called without feature beforeload=yes"); + } + final String url = args.getString(0); + this.cordova.getActivity().runOnUiThread(new Runnable() { + @SuppressLint("NewApi") + @Override + public void run() { + ((InAppBrowserClient)inAppWebView.getWebViewClient()).waitForBeforeload = false; + inAppWebView.loadUrl(url); + } + }); + } else if (action.equals("injectScriptCode")) { String jsWrapper = null; if (args.getBoolean(1)) { @@ -674,6 +690,10 @@ public String showWebPage(final String url, HashMap features) { if (footerColorSet != null) { footerColor = footerColorSet; } + String beforeload = features.get(BEFORELOAD); + if (beforeload != null) { + useBeforeload = beforeload.equals("yes") ? true : false; + } } final CordovaWebView thatWebView = this.webView; @@ -924,7 +944,7 @@ public void openFileChooser(ValueCallback uploadMsg, String acceptType) } }); - WebViewClient client = new InAppBrowserClient(thatWebView, edittext); + WebViewClient client = new InAppBrowserClient(thatWebView, edittext, useBeforeload); inAppWebView.setWebViewClient(client); WebSettings settings = inAppWebView.getSettings(); settings.setJavaScriptEnabled(true); @@ -1085,6 +1105,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) { public class InAppBrowserClient extends WebViewClient { EditText edittext; CordovaWebView webView; + boolean useBeforeload; + boolean waitForBeforeload; /** * Constructor. @@ -1092,9 +1114,11 @@ public class InAppBrowserClient extends WebViewClient { * @param webView * @param mEditText */ - public InAppBrowserClient(CordovaWebView webView, EditText mEditText) { + public InAppBrowserClient(CordovaWebView webView, EditText mEditText, boolean useBeforeload) { this.webView = webView; this.edittext = mEditText; + this.useBeforeload = useBeforeload; + this.waitForBeforeload = useBeforeload; } /** @@ -1107,12 +1131,27 @@ public InAppBrowserClient(CordovaWebView webView, EditText mEditText) { */ @Override public boolean shouldOverrideUrlLoading(WebView webView, String url) { + boolean override = false; + + // On first URL change, initiate JS callback. Only after the beforeload event, continue. + if (this.waitForBeforeload) { + try { + JSONObject obj = new JSONObject(); + obj.put("type", "beforeload"); + obj.put("url", url); + sendUpdate(obj, true); + return true; + } catch (JSONException ex) { + LOG.e(LOG_TAG, "URI passed in has caused a JSON error."); + } + } + if (url.startsWith(WebView.SCHEME_TEL)) { try { Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse(url)); cordova.getActivity().startActivity(intent); - return true; + override = true; } catch (android.content.ActivityNotFoundException e) { LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); } @@ -1121,7 +1160,7 @@ public boolean shouldOverrideUrlLoading(WebView webView, String url) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); cordova.getActivity().startActivity(intent); - return true; + override = true; } catch (android.content.ActivityNotFoundException e) { LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); } @@ -1152,7 +1191,7 @@ else if (url.startsWith("sms:")) { intent.putExtra("address", address); intent.setType("vnd.android-dir/mms-sms"); cordova.getActivity().startActivity(intent); - return true; + override = true; } catch (android.content.ActivityNotFoundException e) { LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); } @@ -1173,7 +1212,7 @@ else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^ obj.put("type", "customscheme"); obj.put("url", url); sendUpdate(obj, true); - return true; + override = true; } catch (JSONException ex) { LOG.e(LOG_TAG, "Custom Scheme URI passed in has caused a JSON error."); } @@ -1182,7 +1221,10 @@ else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^ } } - return false; + if (this.useBeforeload) { + this.waitForBeforeload = true; + } + return override; } @@ -1304,4 +1346,4 @@ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, Str super.onReceivedHttpAuthRequest(view, handler, host, realm); } } -} \ No newline at end of file +} diff --git a/src/ios/CDVInAppBrowser.h b/src/ios/CDVInAppBrowser.h index 66066b9ba..e415f40b8 100644 --- a/src/ios/CDVInAppBrowser.h +++ b/src/ios/CDVInAppBrowser.h @@ -30,7 +30,11 @@ @class CDVInAppBrowserViewController; @interface CDVInAppBrowser : CDVPlugin { - UIWindow * tmpWindow; + UIWindow * tmpWindow; + + @private + BOOL _useBeforeload; + BOOL _waitForBeforeload; } @property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController; @@ -42,6 +46,7 @@ - (void)injectScriptCode:(CDVInvokedUrlCommand*)command; - (void)show:(CDVInvokedUrlCommand*)command; - (void)hide:(CDVInvokedUrlCommand*)command; +- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command; @end @@ -70,6 +75,7 @@ @property (nonatomic, assign) BOOL suppressesincrementalrendering; @property (nonatomic, assign) BOOL hidden; @property (nonatomic, assign) BOOL disallowoverscroll; +@property (nonatomic, assign) BOOL beforeload; + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options; diff --git a/src/ios/CDVInAppBrowser.m b/src/ios/CDVInAppBrowser.m index 0ca3feb15..afb5d215c 100644 --- a/src/ios/CDVInAppBrowser.m +++ b/src/ios/CDVInAppBrowser.m @@ -46,6 +46,8 @@ - (void)pluginInitialize { _previousStatusBarStyle = -1; _callbackIdPattern = nil; + _useBeforeload = NO; + _waitForBeforeload = NO; } - (id)settingForKey:(NSString*)key @@ -209,6 +211,10 @@ - (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering; } + // use of beforeload event + _useBeforeload = browserOptions.beforeload; + _waitForBeforeload = browserOptions.beforeload; + [self.inAppBrowserViewController navigateTo:url]; if (!browserOptions.hidden) { [self show:nil]; @@ -304,6 +310,27 @@ - (void)openInSystem:(NSURL*)url } } +- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command +{ + NSString* urlStr = [command argumentAtIndex:0]; + + if (!_useBeforeload) { + NSLog(@"unexpected loadAfterBeforeload called without feature beforeload=yes"); + } + if (self.inAppBrowserViewController == nil) { + NSLog(@"Tried to invoke loadAfterBeforeload on IAB after it was closed."); + return; + } + if (urlStr == nil) { + NSLog(@"loadAfterBeforeload called with nil argument, ignoring."); + return; + } + + NSURL* url = [NSURL URLWithString:urlStr]; + _waitForBeforeload = NO; + [self.inAppBrowserViewController navigateTo:url]; +} + // 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. // @@ -413,6 +440,7 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest* { NSURL* url = request.URL; BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + BOOL shouldStart = YES; // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute, // and the path, if present, should be a JSON-encoded value to pass to the callback. @@ -440,11 +468,22 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest* return NO; } } + + // When beforeload=yes, on first URL change, initiate JS callback. Only after the beforeload event, continue. + if (_waitForBeforeload && isTopLevelNavigation) { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:@{@"type":@"beforeload", @"url":[url absoluteString]}]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + return NO; + } + //if is an app store link, let the system handle it, otherwise it fails to load it - else if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) { + if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) { [theWebView stopLoading]; [self openInSystem:url]; - return NO; + shouldStart = NO; } else if ((self.callbackId != nil) && isTopLevelNavigation) { // Send a loadstart event for each top-level navigation (includes redirects). @@ -455,7 +494,11 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest* [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; } - return YES; + if (_useBeforeload && isTopLevelNavigation) { + _waitForBeforeload = YES; + } + + return shouldStart; } - (void)webViewDidStartLoad:(UIWebView*)theWebView diff --git a/www/inappbrowser.js b/www/inappbrowser.js index 3619f173f..7764765ad 100644 --- a/www/inappbrowser.js +++ b/www/inappbrowser.js @@ -33,6 +33,7 @@ function InAppBrowser () { this.channels = { + 'beforeload': channel.create('beforeload'), 'loadstart': channel.create('loadstart'), 'loadstop': channel.create('loadstop'), 'loaderror': channel.create('loaderror'), @@ -44,9 +45,17 @@ InAppBrowser.prototype = { _eventHandler: function (event) { if (event && (event.type in this.channels)) { - this.channels[event.type].fire(event); + if (event.type === 'beforeload') { + this.channels[event.type].fire(event, this._loadAfterBeforeload); + } else { + this.channels[event.type].fire(event); + } } }, + _loadAfterBeforeload: function (strUrl) { + strUrl = urlutil.makeAbsolute(strUrl); + exec(null, null, 'InAppBrowser', 'loadAfterBeforeload', [strUrl]); + }, close: function (eventname) { exec(null, null, 'InAppBrowser', 'close', []); }, From d9cafcdb142b0cdb9fd14f410cbef840445440b9 Mon Sep 17 00:00:00 2001 From: Jan Piotrowski Date: Thu, 4 Oct 2018 01:16:19 +0200 Subject: [PATCH 2/2] remove JIRA link --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 9aa1a4382..297732e7d 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,6 @@ Although `window.open` is in the global scope, InAppBrowser is not available unt console.log("window.open works well"); } -Report issues with this plugin on the [Apache Cordova issue tracker](https://issues.apache.org/jira/issues/?jql=project%20%3D%20CB%20AND%20status%20in%20%28Open%2C%20%22In%20Progress%22%2C%20Reopened%29%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20%22cordova-plugin-inappbrowser%22%20ORDER%20BY%20priority%20DESC%2C%20summary%20ASC%2C%20updatedDate%20DESC) - - -## Reference ## Installation cordova plugin add cordova-plugin-inappbrowser