Merge pull request #276 from q-m/feature/beforeload-event

CB-14188: Add beforeload event, catching navigation before it happens
This commit is contained in:
Chris Brody 2018-10-03 18:43:59 -04:00 committed by GitHub
commit eafaedaec4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 14 deletions

View File

@ -115,6 +115,7 @@ instance, or the system browser.
Android supports these additional options: 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. - __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 - __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 - __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. - __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: 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. - __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 - __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 - __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. - __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. - __loadstop__: event fires when the `InAppBrowser` finishes loading a URL.
- __loaderror__: event fires when the `InAppBrowser` encounters an error when 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. - __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. - __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 target = "_blank";
var options = "location=yes,hidden=yes"; var options = "location=yes,hidden=yes,beforeload=yes";
inAppBrowserRef = cordova.InAppBrowser.open(url, target, options); inAppBrowserRef = cordova.InAppBrowser.open(url, target, options);
@ -240,6 +243,8 @@ function showHelp(url) {
inAppBrowserRef.addEventListener('loaderror', loadErrorCallBack); inAppBrowserRef.addEventListener('loaderror', loadErrorCallBack);
inAppBrowserRef.addEventListener('beforeload', beforeloadCallBack);
} }
function loadStartCallBack() { 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 ### InAppBrowserEvent Properties

View File

@ -110,6 +110,7 @@ public class InAppBrowser extends CordovaPlugin {
private static final String HIDE_URL = "hideurlbar"; private static final String HIDE_URL = "hideurlbar";
private static final String FOOTER = "footer"; private static final String FOOTER = "footer";
private static final String FOOTER_COLOR = "footercolor"; 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); 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 hideUrlBar = false;
private boolean showFooter = false; private boolean showFooter = false;
private String footerColor = ""; private String footerColor = "";
private boolean useBeforeload = false;
private String[] allowedSchemes; private String[] allowedSchemes;
/** /**
@ -246,6 +248,20 @@ public class InAppBrowser extends CordovaPlugin {
else if (action.equals("close")) { else if (action.equals("close")) {
closeDialog(); 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")) { else if (action.equals("injectScriptCode")) {
String jsWrapper = null; String jsWrapper = null;
if (args.getBoolean(1)) { if (args.getBoolean(1)) {
@ -674,6 +690,10 @@ public class InAppBrowser extends CordovaPlugin {
if (footerColorSet != null) { if (footerColorSet != null) {
footerColor = footerColorSet; footerColor = footerColorSet;
} }
String beforeload = features.get(BEFORELOAD);
if (beforeload != null) {
useBeforeload = beforeload.equals("yes") ? true : false;
}
} }
final CordovaWebView thatWebView = this.webView; final CordovaWebView thatWebView = this.webView;
@ -924,7 +944,7 @@ public class InAppBrowser extends CordovaPlugin {
} }
}); });
WebViewClient client = new InAppBrowserClient(thatWebView, edittext); WebViewClient client = new InAppBrowserClient(thatWebView, edittext, useBeforeload);
inAppWebView.setWebViewClient(client); inAppWebView.setWebViewClient(client);
WebSettings settings = inAppWebView.getSettings(); WebSettings settings = inAppWebView.getSettings();
settings.setJavaScriptEnabled(true); settings.setJavaScriptEnabled(true);
@ -1085,6 +1105,8 @@ public class InAppBrowser extends CordovaPlugin {
public class InAppBrowserClient extends WebViewClient { public class InAppBrowserClient extends WebViewClient {
EditText edittext; EditText edittext;
CordovaWebView webView; CordovaWebView webView;
boolean useBeforeload;
boolean waitForBeforeload;
/** /**
* Constructor. * Constructor.
@ -1092,9 +1114,11 @@ public class InAppBrowser extends CordovaPlugin {
* @param webView * @param webView
* @param mEditText * @param mEditText
*/ */
public InAppBrowserClient(CordovaWebView webView, EditText mEditText) { public InAppBrowserClient(CordovaWebView webView, EditText mEditText, boolean useBeforeload) {
this.webView = webView; this.webView = webView;
this.edittext = mEditText; this.edittext = mEditText;
this.useBeforeload = useBeforeload;
this.waitForBeforeload = useBeforeload;
} }
/** /**
@ -1107,12 +1131,27 @@ public class InAppBrowser extends CordovaPlugin {
*/ */
@Override @Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) { 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)) { if (url.startsWith(WebView.SCHEME_TEL)) {
try { try {
Intent intent = new Intent(Intent.ACTION_DIAL); Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(url)); intent.setData(Uri.parse(url));
cordova.getActivity().startActivity(intent); cordova.getActivity().startActivity(intent);
return true; override = true;
} catch (android.content.ActivityNotFoundException e) { } catch (android.content.ActivityNotFoundException e) {
LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
} }
@ -1121,7 +1160,7 @@ public class InAppBrowser extends CordovaPlugin {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url)); intent.setData(Uri.parse(url));
cordova.getActivity().startActivity(intent); cordova.getActivity().startActivity(intent);
return true; override = true;
} catch (android.content.ActivityNotFoundException e) { } catch (android.content.ActivityNotFoundException e) {
LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
} }
@ -1152,7 +1191,7 @@ public class InAppBrowser extends CordovaPlugin {
intent.putExtra("address", address); intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms"); intent.setType("vnd.android-dir/mms-sms");
cordova.getActivity().startActivity(intent); cordova.getActivity().startActivity(intent);
return true; override = true;
} catch (android.content.ActivityNotFoundException e) { } catch (android.content.ActivityNotFoundException e) {
LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
} }
@ -1173,7 +1212,7 @@ public class InAppBrowser extends CordovaPlugin {
obj.put("type", "customscheme"); obj.put("type", "customscheme");
obj.put("url", url); obj.put("url", url);
sendUpdate(obj, true); sendUpdate(obj, true);
return true; override = true;
} catch (JSONException ex) { } catch (JSONException ex) {
LOG.e(LOG_TAG, "Custom Scheme URI passed in has caused a JSON error."); LOG.e(LOG_TAG, "Custom Scheme URI passed in has caused a JSON error.");
} }
@ -1182,7 +1221,10 @@ public class InAppBrowser extends CordovaPlugin {
} }
} }
return false; if (this.useBeforeload) {
this.waitForBeforeload = true;
}
return override;
} }

View File

@ -31,6 +31,10 @@
@interface CDVInAppBrowser : CDVPlugin { @interface CDVInAppBrowser : CDVPlugin {
UIWindow * tmpWindow; UIWindow * tmpWindow;
@private
BOOL _useBeforeload;
BOOL _waitForBeforeload;
} }
@property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController; @property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController;
@ -42,6 +46,7 @@
- (void)injectScriptCode:(CDVInvokedUrlCommand*)command; - (void)injectScriptCode:(CDVInvokedUrlCommand*)command;
- (void)show:(CDVInvokedUrlCommand*)command; - (void)show:(CDVInvokedUrlCommand*)command;
- (void)hide:(CDVInvokedUrlCommand*)command; - (void)hide:(CDVInvokedUrlCommand*)command;
- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command;
@end @end
@ -70,6 +75,7 @@
@property (nonatomic, assign) BOOL suppressesincrementalrendering; @property (nonatomic, assign) BOOL suppressesincrementalrendering;
@property (nonatomic, assign) BOOL hidden; @property (nonatomic, assign) BOOL hidden;
@property (nonatomic, assign) BOOL disallowoverscroll; @property (nonatomic, assign) BOOL disallowoverscroll;
@property (nonatomic, assign) BOOL beforeload;
+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options; + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options;

View File

@ -46,6 +46,8 @@
{ {
_previousStatusBarStyle = -1; _previousStatusBarStyle = -1;
_callbackIdPattern = nil; _callbackIdPattern = nil;
_useBeforeload = NO;
_waitForBeforeload = NO;
} }
- (id)settingForKey:(NSString*)key - (id)settingForKey:(NSString*)key
@ -209,6 +211,10 @@
self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering; self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
} }
// use of beforeload event
_useBeforeload = browserOptions.beforeload;
_waitForBeforeload = browserOptions.beforeload;
[self.inAppBrowserViewController navigateTo:url]; [self.inAppBrowserViewController navigateTo:url];
if (!browserOptions.hidden) { if (!browserOptions.hidden) {
[self show:nil]; [self show:nil];
@ -304,6 +310,27 @@
} }
} }
- (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 // 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. // provides a consistent method for injecting JavaScript code into the document.
// //
@ -413,6 +440,7 @@
{ {
NSURL* url = request.URL; NSURL* url = request.URL;
BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; 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, // 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. // and the path, if present, should be a JSON-encoded value to pass to the callback.
@ -440,11 +468,22 @@
return NO; 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 //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]; [theWebView stopLoading];
[self openInSystem:url]; [self openInSystem:url];
return NO; shouldStart = NO;
} }
else if ((self.callbackId != nil) && isTopLevelNavigation) { else if ((self.callbackId != nil) && isTopLevelNavigation) {
// Send a loadstart event for each top-level navigation (includes redirects). // Send a loadstart event for each top-level navigation (includes redirects).
@ -455,7 +494,11 @@
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
} }
return YES; if (_useBeforeload && isTopLevelNavigation) {
_waitForBeforeload = YES;
}
return shouldStart;
} }
- (void)webViewDidStartLoad:(UIWebView*)theWebView - (void)webViewDidStartLoad:(UIWebView*)theWebView

View File

@ -33,6 +33,7 @@
function InAppBrowser () { function InAppBrowser () {
this.channels = { this.channels = {
'beforeload': channel.create('beforeload'),
'loadstart': channel.create('loadstart'), 'loadstart': channel.create('loadstart'),
'loadstop': channel.create('loadstop'), 'loadstop': channel.create('loadstop'),
'loaderror': channel.create('loaderror'), 'loaderror': channel.create('loaderror'),
@ -44,8 +45,16 @@
InAppBrowser.prototype = { InAppBrowser.prototype = {
_eventHandler: function (event) { _eventHandler: function (event) {
if (event && (event.type in this.channels)) { if (event && (event.type in this.channels)) {
if (event.type === 'beforeload') {
this.channels[event.type].fire(event, this._loadAfterBeforeload);
} else {
this.channels[event.type].fire(event); this.channels[event.type].fire(event);
} }
}
},
_loadAfterBeforeload: function (strUrl) {
strUrl = urlutil.makeAbsolute(strUrl);
exec(null, null, 'InAppBrowser', 'loadAfterBeforeload', [strUrl]);
}, },
close: function (eventname) { close: function (eventname) {
exec(null, null, 'InAppBrowser', 'close', []); exec(null, null, 'InAppBrowser', 'close', []);