Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-14188: Add beforeload event, catching navigation before it happens #276

Merged
merged 1 commit into from
Oct 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.

Expand All @@ -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);

Expand All @@ -240,6 +243,8 @@ function showHelp(url) {

inAppBrowserRef.addEventListener('loaderror', loadErrorCallBack);

inAppBrowserRef.addEventListener('beforeload', beforeloadCallBack);

}

function loadStartCallBack() {
Expand Down Expand Up @@ -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
Expand Down
58 changes: 50 additions & 8 deletions src/android/InAppBrowser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -674,6 +690,10 @@ public String showWebPage(final String url, HashMap<String, String> 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;
Expand Down Expand Up @@ -924,7 +944,7 @@ public void openFileChooser(ValueCallback<Uri> 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);
Expand Down Expand Up @@ -1085,16 +1105,20 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
public class InAppBrowserClient extends WebViewClient {
EditText edittext;
CordovaWebView webView;
boolean useBeforeload;
boolean waitForBeforeload;

/**
* Constructor.
*
* @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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 2 vars that will always store the same value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is merged already, but perhaps my reply will serve as a reference)
waitForBeforeload is the current state, whether we need to wait a call back from JS to really load next time it is encountered. useBeforeload indicates whether the beforeload event is used at all (see the end of shouldOverrideUrlLoading).

this.waitForBeforeload = useBeforeload;
}

/**
Expand All @@ -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());
}
Expand All @@ -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());
}
Expand Down Expand Up @@ -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());
}
Expand All @@ -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.");
}
Expand All @@ -1182,7 +1221,10 @@ else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^
}
}

return false;
if (this.useBeforeload) {
this.waitForBeforeload = true;
}
return override;
}


Expand Down Expand Up @@ -1304,4 +1346,4 @@ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, Str
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
}
}
}
8 changes: 7 additions & 1 deletion src/ios/CDVInAppBrowser.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
@class CDVInAppBrowserViewController;

@interface CDVInAppBrowser : CDVPlugin {
UIWindow * tmpWindow;
UIWindow * tmpWindow;

@private
BOOL _useBeforeload;
BOOL _waitForBeforeload;
}

@property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController;
Expand All @@ -42,6 +46,7 @@
- (void)injectScriptCode:(CDVInvokedUrlCommand*)command;
- (void)show:(CDVInvokedUrlCommand*)command;
- (void)hide:(CDVInvokedUrlCommand*)command;
- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command;

@end

Expand Down Expand Up @@ -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;

Expand Down
49 changes: 46 additions & 3 deletions src/ios/CDVInAppBrowser.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ - (void)pluginInitialize
{
_previousStatusBarStyle = -1;
_callbackIdPattern = nil;
_useBeforeload = NO;
_waitForBeforeload = NO;
}

- (id)settingForKey:(NSString*)key
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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).
Expand All @@ -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
Expand Down
11 changes: 10 additions & 1 deletion www/inappbrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

function InAppBrowser () {
this.channels = {
'beforeload': channel.create('beforeload'),
'loadstart': channel.create('loadstart'),
'loadstop': channel.create('loadstop'),
'loaderror': channel.create('loaderror'),
Expand All @@ -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', []);
},
Expand Down