Skip to content

Commit eafaeda

Browse files
author
Chris Brody
authored
Merge pull request #276 from q-m/feature/beforeload-event
CB-14188: Add beforeload event, catching navigation before it happens
2 parents a0c267f + 228703a commit eafaeda

File tree

5 files changed

+133
-14
lines changed

5 files changed

+133
-14
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ instance, or the system browser.
115115
Android supports these additional options:
116116

117117
- __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.
118+
- __beforeload__: set to `yes` to enable the `beforeload` event to modify which pages are actually loaded in the browser.
118119
- __clearcache__: set to `yes` to have the browser's cookie cache cleared before the new window is opened
119120
- __clearsessioncache__: set to `yes` to have the session cookie cache cleared before the new window is opened
120121
- __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.
137138
iOS supports these additional options:
138139

139140
- __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.
141+
- __beforeload__: set to `yes` to enable the `beforeload` event to modify which pages are actually loaded in the browser.
140142
- __clearcache__: set to `yes` to have the browser's cookie cache cleared before the new window is opened
141143
- __clearsessioncache__: set to `yes` to have the session cookie cache cleared before the new window is opened
142144
- __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
217219
- __loadstop__: event fires when the `InAppBrowser` finishes loading a URL.
218220
- __loaderror__: event fires when the `InAppBrowser` encounters an error when loading a URL.
219221
- __exit__: event fires when the `InAppBrowser` window is closed.
222+
- __beforeload__: event fires when the `InAppBrowser` decides whether to load an URL or not (only with option `beforeload=yes`).
220223

221224
- __callback__: the function that executes when the event fires. The function is passed an `InAppBrowserEvent` object as a parameter.
222225

@@ -230,7 +233,7 @@ function showHelp(url) {
230233

231234
var target = "_blank";
232235

233-
var options = "location=yes,hidden=yes";
236+
var options = "location=yes,hidden=yes,beforeload=yes";
234237

235238
inAppBrowserRef = cordova.InAppBrowser.open(url, target, options);
236239

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

241244
inAppBrowserRef.addEventListener('loaderror', loadErrorCallBack);
242245

246+
inAppBrowserRef.addEventListener('beforeload', beforeloadCallBack);
247+
243248
}
244249

245250
function loadStartCallBack() {
@@ -288,6 +293,20 @@ function executeScriptCallBack(params) {
288293

289294
}
290295

296+
function beforeloadCallback(params, callback) {
297+
298+
if (params.url.startsWith("http://www.example.com/")) {
299+
300+
// Load this URL in the inAppBrowser.
301+
callback(params.url);
302+
} else {
303+
304+
// The callback is not invoked, so the page will not be loaded.
305+
$('#status-message').text("This browser only opens pages on http://www.example.com/");
306+
}
307+
308+
}
309+
291310
```
292311

293312
### InAppBrowserEvent Properties

src/android/InAppBrowser.java

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public class InAppBrowser extends CordovaPlugin {
110110
private static final String HIDE_URL = "hideurlbar";
111111
private static final String FOOTER = "footer";
112112
private static final String FOOTER_COLOR = "footercolor";
113+
private static final String BEFORELOAD = "beforeload";
113114

114115
private static final List customizableOptions = Arrays.asList(CLOSE_BUTTON_CAPTION, TOOLBAR_COLOR, NAVIGATION_COLOR, CLOSE_BUTTON_COLOR, FOOTER_COLOR);
115116

@@ -138,6 +139,7 @@ public class InAppBrowser extends CordovaPlugin {
138139
private boolean hideUrlBar = false;
139140
private boolean showFooter = false;
140141
private String footerColor = "";
142+
private boolean useBeforeload = false;
141143
private String[] allowedSchemes;
142144

143145
/**
@@ -246,6 +248,20 @@ else if (SYSTEM.equals(target)) {
246248
else if (action.equals("close")) {
247249
closeDialog();
248250
}
251+
else if (action.equals("loadAfterBeforeload")) {
252+
if (!useBeforeload) {
253+
LOG.e(LOG_TAG, "unexpected loadAfterBeforeload called without feature beforeload=yes");
254+
}
255+
final String url = args.getString(0);
256+
this.cordova.getActivity().runOnUiThread(new Runnable() {
257+
@SuppressLint("NewApi")
258+
@Override
259+
public void run() {
260+
((InAppBrowserClient)inAppWebView.getWebViewClient()).waitForBeforeload = false;
261+
inAppWebView.loadUrl(url);
262+
}
263+
});
264+
}
249265
else if (action.equals("injectScriptCode")) {
250266
String jsWrapper = null;
251267
if (args.getBoolean(1)) {
@@ -674,6 +690,10 @@ public String showWebPage(final String url, HashMap<String, String> features) {
674690
if (footerColorSet != null) {
675691
footerColor = footerColorSet;
676692
}
693+
String beforeload = features.get(BEFORELOAD);
694+
if (beforeload != null) {
695+
useBeforeload = beforeload.equals("yes") ? true : false;
696+
}
677697
}
678698

679699
final CordovaWebView thatWebView = this.webView;
@@ -924,7 +944,7 @@ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType)
924944
}
925945

926946
});
927-
WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
947+
WebViewClient client = new InAppBrowserClient(thatWebView, edittext, useBeforeload);
928948
inAppWebView.setWebViewClient(client);
929949
WebSettings settings = inAppWebView.getSettings();
930950
settings.setJavaScriptEnabled(true);
@@ -1085,16 +1105,20 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
10851105
public class InAppBrowserClient extends WebViewClient {
10861106
EditText edittext;
10871107
CordovaWebView webView;
1108+
boolean useBeforeload;
1109+
boolean waitForBeforeload;
10881110

10891111
/**
10901112
* Constructor.
10911113
*
10921114
* @param webView
10931115
* @param mEditText
10941116
*/
1095-
public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
1117+
public InAppBrowserClient(CordovaWebView webView, EditText mEditText, boolean useBeforeload) {
10961118
this.webView = webView;
10971119
this.edittext = mEditText;
1120+
this.useBeforeload = useBeforeload;
1121+
this.waitForBeforeload = useBeforeload;
10981122
}
10991123

11001124
/**
@@ -1107,12 +1131,27 @@ public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
11071131
*/
11081132
@Override
11091133
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
1134+
boolean override = false;
1135+
1136+
// On first URL change, initiate JS callback. Only after the beforeload event, continue.
1137+
if (this.waitForBeforeload) {
1138+
try {
1139+
JSONObject obj = new JSONObject();
1140+
obj.put("type", "beforeload");
1141+
obj.put("url", url);
1142+
sendUpdate(obj, true);
1143+
return true;
1144+
} catch (JSONException ex) {
1145+
LOG.e(LOG_TAG, "URI passed in has caused a JSON error.");
1146+
}
1147+
}
1148+
11101149
if (url.startsWith(WebView.SCHEME_TEL)) {
11111150
try {
11121151
Intent intent = new Intent(Intent.ACTION_DIAL);
11131152
intent.setData(Uri.parse(url));
11141153
cordova.getActivity().startActivity(intent);
1115-
return true;
1154+
override = true;
11161155
} catch (android.content.ActivityNotFoundException e) {
11171156
LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
11181157
}
@@ -1121,7 +1160,7 @@ public boolean shouldOverrideUrlLoading(WebView webView, String url) {
11211160
Intent intent = new Intent(Intent.ACTION_VIEW);
11221161
intent.setData(Uri.parse(url));
11231162
cordova.getActivity().startActivity(intent);
1124-
return true;
1163+
override = true;
11251164
} catch (android.content.ActivityNotFoundException e) {
11261165
LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
11271166
}
@@ -1152,7 +1191,7 @@ else if (url.startsWith("sms:")) {
11521191
intent.putExtra("address", address);
11531192
intent.setType("vnd.android-dir/mms-sms");
11541193
cordova.getActivity().startActivity(intent);
1155-
return true;
1194+
override = true;
11561195
} catch (android.content.ActivityNotFoundException e) {
11571196
LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
11581197
}
@@ -1173,7 +1212,7 @@ else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^
11731212
obj.put("type", "customscheme");
11741213
obj.put("url", url);
11751214
sendUpdate(obj, true);
1176-
return true;
1215+
override = true;
11771216
} catch (JSONException ex) {
11781217
LOG.e(LOG_TAG, "Custom Scheme URI passed in has caused a JSON error.");
11791218
}
@@ -1182,7 +1221,10 @@ else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^
11821221
}
11831222
}
11841223

1185-
return false;
1224+
if (this.useBeforeload) {
1225+
this.waitForBeforeload = true;
1226+
}
1227+
return override;
11861228
}
11871229

11881230

@@ -1304,4 +1346,4 @@ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, Str
13041346
super.onReceivedHttpAuthRequest(view, handler, host, realm);
13051347
}
13061348
}
1307-
}
1349+
}

src/ios/CDVInAppBrowser.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030
@class CDVInAppBrowserViewController;
3131

3232
@interface CDVInAppBrowser : CDVPlugin {
33-
UIWindow * tmpWindow;
33+
UIWindow * tmpWindow;
34+
35+
@private
36+
BOOL _useBeforeload;
37+
BOOL _waitForBeforeload;
3438
}
3539

3640
@property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController;
@@ -42,6 +46,7 @@
4246
- (void)injectScriptCode:(CDVInvokedUrlCommand*)command;
4347
- (void)show:(CDVInvokedUrlCommand*)command;
4448
- (void)hide:(CDVInvokedUrlCommand*)command;
49+
- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command;
4550

4651
@end
4752

@@ -70,6 +75,7 @@
7075
@property (nonatomic, assign) BOOL suppressesincrementalrendering;
7176
@property (nonatomic, assign) BOOL hidden;
7277
@property (nonatomic, assign) BOOL disallowoverscroll;
78+
@property (nonatomic, assign) BOOL beforeload;
7379

7480
+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options;
7581

src/ios/CDVInAppBrowser.m

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ - (void)pluginInitialize
4646
{
4747
_previousStatusBarStyle = -1;
4848
_callbackIdPattern = nil;
49+
_useBeforeload = NO;
50+
_waitForBeforeload = NO;
4951
}
5052

5153
- (id)settingForKey:(NSString*)key
@@ -209,6 +211,10 @@ - (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
209211
self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
210212
}
211213

214+
// use of beforeload event
215+
_useBeforeload = browserOptions.beforeload;
216+
_waitForBeforeload = browserOptions.beforeload;
217+
212218
[self.inAppBrowserViewController navigateTo:url];
213219
if (!browserOptions.hidden) {
214220
[self show:nil];
@@ -304,6 +310,27 @@ - (void)openInSystem:(NSURL*)url
304310
}
305311
}
306312

313+
- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command
314+
{
315+
NSString* urlStr = [command argumentAtIndex:0];
316+
317+
if (!_useBeforeload) {
318+
NSLog(@"unexpected loadAfterBeforeload called without feature beforeload=yes");
319+
}
320+
if (self.inAppBrowserViewController == nil) {
321+
NSLog(@"Tried to invoke loadAfterBeforeload on IAB after it was closed.");
322+
return;
323+
}
324+
if (urlStr == nil) {
325+
NSLog(@"loadAfterBeforeload called with nil argument, ignoring.");
326+
return;
327+
}
328+
329+
NSURL* url = [NSURL URLWithString:urlStr];
330+
_waitForBeforeload = NO;
331+
[self.inAppBrowserViewController navigateTo:url];
332+
}
333+
307334
// This is a helper method for the inject{Script|Style}{Code|File} API calls, which
308335
// provides a consistent method for injecting JavaScript code into the document.
309336
//
@@ -413,6 +440,7 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*
413440
{
414441
NSURL* url = request.URL;
415442
BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
443+
BOOL shouldStart = YES;
416444

417445
// See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
418446
// 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*
440468
return NO;
441469
}
442470
}
471+
472+
// When beforeload=yes, on first URL change, initiate JS callback. Only after the beforeload event, continue.
473+
if (_waitForBeforeload && isTopLevelNavigation) {
474+
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
475+
messageAsDictionary:@{@"type":@"beforeload", @"url":[url absoluteString]}];
476+
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
477+
478+
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
479+
return NO;
480+
}
481+
443482
//if is an app store link, let the system handle it, otherwise it fails to load it
444-
else if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) {
483+
if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) {
445484
[theWebView stopLoading];
446485
[self openInSystem:url];
447-
return NO;
486+
shouldStart = NO;
448487
}
449488
else if ((self.callbackId != nil) && isTopLevelNavigation) {
450489
// Send a loadstart event for each top-level navigation (includes redirects).
@@ -455,7 +494,11 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*
455494
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
456495
}
457496

458-
return YES;
497+
if (_useBeforeload && isTopLevelNavigation) {
498+
_waitForBeforeload = YES;
499+
}
500+
501+
return shouldStart;
459502
}
460503

461504
- (void)webViewDidStartLoad:(UIWebView*)theWebView

www/inappbrowser.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
function InAppBrowser () {
3535
this.channels = {
36+
'beforeload': channel.create('beforeload'),
3637
'loadstart': channel.create('loadstart'),
3738
'loadstop': channel.create('loadstop'),
3839
'loaderror': channel.create('loaderror'),
@@ -44,9 +45,17 @@
4445
InAppBrowser.prototype = {
4546
_eventHandler: function (event) {
4647
if (event && (event.type in this.channels)) {
47-
this.channels[event.type].fire(event);
48+
if (event.type === 'beforeload') {
49+
this.channels[event.type].fire(event, this._loadAfterBeforeload);
50+
} else {
51+
this.channels[event.type].fire(event);
52+
}
4853
}
4954
},
55+
_loadAfterBeforeload: function (strUrl) {
56+
strUrl = urlutil.makeAbsolute(strUrl);
57+
exec(null, null, 'InAppBrowser', 'loadAfterBeforeload', [strUrl]);
58+
},
5059
close: function (eventname) {
5160
exec(null, null, 'InAppBrowser', 'close', []);
5261
},

0 commit comments

Comments
 (0)