Skip to content

Commit 632a395

Browse files
dpa99cjanpio
authored andcommitted
GH-359: Fix beforeload to work with POST requests (#367)
### Platforms affected iOS and Android ### What does this PR do? Fixes the behaviour of `beforeload` to resolve the problem with POST requests outlined in #359. The `beforeload` parameter has been changed from taking only a boolean (`yes` or not defined) to a discrete string with possible values of `get`, `post`, or `yes` which correspond to request types of GET, POST or GET&POST respectively. The `README.md` has been updated to reflect this. Note that use of `beforeload` to intercept POST requests is currently not supported on Android or iOS, so if `beforeload=yes` is specified and a POST request is detected as the HTTP request method, `beforeload` behaviour will not be applied. If `beforeload=post` is specified, a `loaderror` event will be dispatched which states that POST requests are not yet supported. #### Notes for Android The `shouldOverrideUrlLoading()` override method has been updated to support the [new method interface added in API 24 / Android 7][1] which receives the `WebResourceRequest` instead of just the `String url`, enabling the HTTP method of the request to be determined. The [deprecated method interface][2] has also been preserved for API <=23, but in this case the HTTP method cannot be determined so is passed as null. Also note that due to a [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=155250), `shouldOverrideUrlLoading()` is currently not called for POST requests. It's possible this may be resolved in a future Chromium version in the Android System Webview (given that this is now self-updating and independent of Android version since Android 5.0) - in prospective anticipation of this, code to handle POST requests has been added to `shouldOverrideUrlLoading()`. However, it seems more likely that this won't be resolved any time soon given that [a Chromium dev said](https://bugs.chromium.org/p/chromium/issues/detail?id=155250#c39): > We're looking at implementing a better way to handle request interception in a future OS version. There's no way to just "fix" this, the API doesn't accommodate this usage at all. This will not be something you can use any time soon. Therefore if we want to go ahead and use `beforeload` to intercept request types other than GET, it's likely we'll instead need to use the `shouldInterceptRequest()` method override. As with `shouldOverrideUrlLoading()`, there are a two variants: the [new method interface][3] added in API 21 / Android 5.0 which which receives the `WebResourceRequest` object and the [deprecated one][4] which receives only `String url`. If we want to determine the HTTP request method, we'll need to use the new implementation. This has been empirically tested and *is* called for POST requests so would allow the possibility to intercept, delay, modify and send the POST request and its data via `beforeload`. Both `shouldInterceptRequest()` method interfaces have been exposed in the Android implentation for potential future use but they currently do nothing other than return the unadulterated request object. ### What testing has been done on this change? Manual testing of POST and GET requests on both platforms using a test app container: https://github.com/dpa99c/cordova-plugin-inappbrowser-test [1]: https://developer.android.com/reference/android/webkit/WebViewClient.html#shouldOverrideUrlLoading(android.webkit.WebView,%20android.webkit.WebResourceRequest) [2]: https://developer.android.com/reference/android/webkit/WebViewClient.html#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String) [3]: https://developer.android.com/reference/android/webkit/WebViewClient.html#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest) [4]: https://developer.android.com/reference/android/webkit/WebViewClient.html#shouldInterceptRequest(android.webkit.WebView,%20java.lang.String)
1 parent c54d100 commit 632a395

File tree

8 files changed

+199
-42
lines changed

8 files changed

+199
-42
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ instance, or the system browser.
106106
Android supports these additional options:
107107

108108
- __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.
109-
- __beforeload__: set to `yes` to enable the `beforeload` event to modify which pages are actually loaded in the browser.
109+
- __beforeload__: set to enable the `beforeload` event to modify which pages are actually loaded in the browser. Accepted values are `get` to intercept only GET requests, `post` to intercept on POST requests or `yes` to intercept both GET & POST requests. Note that POST requests are not currently supported and will be ignored (if you set `beforeload=post` it will raise an error).
110110
- __clearcache__: set to `yes` to have the browser's cookie cache cleared before the new window is opened
111111
- __clearsessioncache__: set to `yes` to have the session cookie cache cleared before the new window is opened
112112
- __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.
@@ -130,7 +130,7 @@ instance, or the system browser.
130130

131131
- __usewkwebview__: set to `yes` to use WKWebView engine for the InappBrowser. Omit or set to `no` (default) to use UIWebView. Note: Using `usewkwebview=yes` requires that a WKWebView engine plugin be installed in the Cordova project (e.g. [cordova-plugin-wkwebview-engine](https://github.com/apache/cordova-plugin-wkwebview-engine) or [cordova-plugin-ionic-webview](https://github.com/ionic-team/cordova-plugin-ionic-webview)).
132132
- __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.
133-
- __beforeload__: set to `yes` to enable the `beforeload` event to modify which pages are actually loaded in the browser.
133+
- __beforeload__: set to enable the `beforeload` event to modify which pages are actually loaded in the browser. Accepted values are `get` to intercept only GET requests, `post` to intercept on POST requests or `yes` to intercept both GET & POST requests. Note that POST requests are not currently supported and will be ignored (if you set `beforeload=post` it will raise an error).
134134
- __clearcache__: set to `yes` to have the browser's cookie cache cleared before the new window is opened
135135
- __clearsessioncache__: set to `yes` to have the session cookie cache cleared before the new window is opened. For WKWebView, requires iOS 11+ on target device.
136136
- __cleardata__: set to `yes` to have the browser's entire local storage cleared (cookies, HTML5 local storage, IndexedDB, etc.) before the new window is opened
@@ -212,7 +212,7 @@ The object returned from a call to `cordova.InAppBrowser.open` when the target i
212212
- __loadstop__: event fires when the `InAppBrowser` finishes loading a URL.
213213
- __loaderror__: event fires when the `InAppBrowser` encounters an error when loading a URL.
214214
- __exit__: event fires when the `InAppBrowser` window is closed.
215-
- __beforeload__: event fires when the `InAppBrowser` decides whether to load an URL or not (only with option `beforeload=yes`).
215+
- __beforeload__: event fires when the `InAppBrowser` decides whether to load an URL or not (only with option `beforeload` set).
216216
- __message__: event fires when the `InAppBrowser` receives a message posted from the page loaded inside the `InAppBrowser` Webview.
217217

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

src/android/InAppBrowser.java

Lines changed: 118 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Licensed to the Apache Software Foundation (ASF) under one
1919
package org.apache.cordova.inappbrowser;
2020

2121
import android.annotation.SuppressLint;
22+
import android.annotation.TargetApi;
2223
import android.content.ComponentName;
2324
import android.content.Context;
2425
import android.content.Intent;
@@ -51,6 +52,8 @@ Licensed to the Apache Software Foundation (ASF) under one
5152
import android.webkit.JavascriptInterface;
5253
import android.webkit.ValueCallback;
5354
import android.webkit.WebChromeClient;
55+
import android.webkit.WebResourceRequest;
56+
import android.webkit.WebResourceResponse;
5457
import android.webkit.WebSettings;
5558
import android.webkit.WebView;
5659
import android.webkit.WebViewClient;
@@ -141,7 +144,7 @@ public class InAppBrowser extends CordovaPlugin {
141144
private boolean hideUrlBar = false;
142145
private boolean showFooter = false;
143146
private String footerColor = "";
144-
private boolean useBeforeload = false;
147+
private String beforeload = "";
145148
private String[] allowedSchemes;
146149

147150
/**
@@ -251,8 +254,8 @@ else if (action.equals("close")) {
251254
closeDialog();
252255
}
253256
else if (action.equals("loadAfterBeforeload")) {
254-
if (!useBeforeload) {
255-
LOG.e(LOG_TAG, "unexpected loadAfterBeforeload called without feature beforeload=yes");
257+
if (beforeload == null) {
258+
LOG.e(LOG_TAG, "unexpected loadAfterBeforeload called without feature beforeload=yes");
256259
}
257260
final String url = args.getString(0);
258261
this.cordova.getActivity().runOnUiThread(new Runnable() {
@@ -692,9 +695,8 @@ public String showWebPage(final String url, HashMap<String, String> features) {
692695
if (footerColorSet != null) {
693696
footerColor = footerColorSet;
694697
}
695-
String beforeload = features.get(BEFORELOAD);
696-
if (beforeload != null) {
697-
useBeforeload = beforeload.equals("yes") ? true : false;
698+
if (features.get(BEFORELOAD) != null) {
699+
beforeload = features.get(BEFORELOAD);
698700
}
699701
}
700702

@@ -946,7 +948,7 @@ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType)
946948
}
947949

948950
});
949-
WebViewClient client = new InAppBrowserClient(thatWebView, edittext, useBeforeload);
951+
WebViewClient client = new InAppBrowserClient(thatWebView, edittext, beforeload);
950952
inAppWebView.setWebViewClient(client);
951953
WebSettings settings = inAppWebView.getSettings();
952954
settings.setJavaScriptEnabled(true);
@@ -1123,7 +1125,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
11231125
public class InAppBrowserClient extends WebViewClient {
11241126
EditText edittext;
11251127
CordovaWebView webView;
1126-
boolean useBeforeload;
1128+
String beforeload;
11271129
boolean waitForBeforeload;
11281130

11291131
/**
@@ -1132,35 +1134,86 @@ public class InAppBrowserClient extends WebViewClient {
11321134
* @param webView
11331135
* @param mEditText
11341136
*/
1135-
public InAppBrowserClient(CordovaWebView webView, EditText mEditText, boolean useBeforeload) {
1137+
public InAppBrowserClient(CordovaWebView webView, EditText mEditText, String beforeload) {
11361138
this.webView = webView;
11371139
this.edittext = mEditText;
1138-
this.useBeforeload = useBeforeload;
1139-
this.waitForBeforeload = useBeforeload;
1140+
this.beforeload = beforeload;
1141+
this.waitForBeforeload = beforeload != null;
11401142
}
11411143

11421144
/**
11431145
* Override the URL that should be loaded
11441146
*
1145-
* This handles a small subset of all the URIs that would be encountered.
1147+
* Legacy (deprecated in API 24)
1148+
* For Android 6 and below.
11461149
*
11471150
* @param webView
11481151
* @param url
11491152
*/
1153+
@SuppressWarnings("deprecation")
11501154
@Override
11511155
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
1156+
return shouldOverrideUrlLoading(url, null);
1157+
}
1158+
1159+
/**
1160+
* Override the URL that should be loaded
1161+
*
1162+
* New (added in API 24)
1163+
* For Android 7 and above.
1164+
*
1165+
* @param webView
1166+
* @param request
1167+
*/
1168+
@TargetApi(Build.VERSION_CODES.N)
1169+
@Override
1170+
public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest request) {
1171+
return shouldOverrideUrlLoading(request.getUrl().toString(), request.getMethod());
1172+
}
1173+
1174+
/**
1175+
* Override the URL that should be loaded
1176+
*
1177+
* This handles a small subset of all the URIs that would be encountered.
1178+
*
1179+
* @param url
1180+
* @param method
1181+
*/
1182+
public boolean shouldOverrideUrlLoading(String url, String method) {
11521183
boolean override = false;
1184+
boolean useBeforeload = false;
1185+
String errorMessage = null;
1186+
1187+
if(beforeload.equals("yes")
1188+
//TODO handle POST requests then this condition can be removed:
1189+
&& !method.equals("POST"))
1190+
{
1191+
useBeforeload = true;
1192+
}else if(beforeload.equals("get") && (method == null || method.equals("GET"))){
1193+
useBeforeload = true;
1194+
}else if(beforeload.equals("post") && (method == null || method.equals("POST"))){
1195+
//TODO handle POST requests
1196+
errorMessage = "beforeload doesn't yet support POST requests";
1197+
}
11531198

11541199
// On first URL change, initiate JS callback. Only after the beforeload event, continue.
1155-
if (this.waitForBeforeload) {
1200+
if (useBeforeload && this.waitForBeforeload) {
1201+
if(sendBeforeLoad(url, method)){
1202+
return true;
1203+
}
1204+
}
1205+
1206+
if(errorMessage != null){
11561207
try {
1208+
LOG.e(LOG_TAG, errorMessage);
11571209
JSONObject obj = new JSONObject();
1158-
obj.put("type", "beforeload");
1210+
obj.put("type", LOAD_ERROR_EVENT);
11591211
obj.put("url", url);
1160-
sendUpdate(obj, true);
1161-
return true;
1162-
} catch (JSONException ex) {
1163-
LOG.e(LOG_TAG, "URI passed in has caused a JSON error.");
1212+
obj.put("code", -1);
1213+
obj.put("message", errorMessage);
1214+
sendUpdate(obj, true, PluginResult.Status.ERROR);
1215+
}catch(Exception e){
1216+
LOG.e(LOG_TAG, "Error sending loaderror for " + url + ": " + e.toString());
11641217
}
11651218
}
11661219

@@ -1239,12 +1292,58 @@ else if (!url.startsWith("http:") && !url.startsWith("https:") && url.matches("^
12391292
}
12401293
}
12411294

1242-
if (this.useBeforeload) {
1295+
if (useBeforeload) {
12431296
this.waitForBeforeload = true;
12441297
}
12451298
return override;
12461299
}
12471300

1301+
private boolean sendBeforeLoad(String url, String method){
1302+
try {
1303+
JSONObject obj = new JSONObject();
1304+
obj.put("type", "beforeload");
1305+
obj.put("url", url);
1306+
if(method != null){
1307+
obj.put("method", method);
1308+
}
1309+
sendUpdate(obj, true);
1310+
return true;
1311+
} catch (JSONException ex) {
1312+
LOG.e(LOG_TAG, "URI passed in has caused a JSON error.");
1313+
}
1314+
return false;
1315+
}
1316+
1317+
1318+
/**
1319+
* Legacy (deprecated in API 21)
1320+
* For Android 4.4 and below.
1321+
* @param view
1322+
* @param url
1323+
* @return
1324+
*/
1325+
@SuppressWarnings("deprecation")
1326+
@Override
1327+
public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
1328+
return shouldInterceptRequest(url, super.shouldInterceptRequest(view, url), null);
1329+
}
1330+
1331+
/**
1332+
* New (added in API 21)
1333+
* For Android 5.0 and above.
1334+
*
1335+
* @param webView
1336+
* @param request
1337+
*/
1338+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
1339+
@Override
1340+
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
1341+
return shouldInterceptRequest(request.getUrl().toString(), super.shouldInterceptRequest(view, request), request.getMethod());
1342+
}
1343+
1344+
public WebResourceResponse shouldInterceptRequest(String url, WebResourceResponse response, String method){
1345+
return response;
1346+
}
12481347

12491348
/*
12501349
* onPageStarted fires the LOAD_START_EVENT

src/ios/CDVInAppBrowserOptions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
@property (nonatomic, assign) BOOL suppressesincrementalrendering;
4646
@property (nonatomic, assign) BOOL hidden;
4747
@property (nonatomic, assign) BOOL disallowoverscroll;
48-
@property (nonatomic, assign) BOOL beforeload;
48+
@property (nonatomic, copy) NSString* beforeload;
4949

5050
+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options;
5151

src/ios/CDVInAppBrowserOptions.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ - (id)init
4646
self.closebuttoncolor = nil;
4747
self.toolbarcolor = nil;
4848
self.toolbartranslucent = YES;
49+
self.beforeload = @"";
4950
}
5051

5152
return self;

src/ios/CDVUIInAppBrowser.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
UIWindow * tmpWindow;
3636

3737
@private
38-
BOOL _useBeforeload;
38+
NSString* _beforeload;
3939
BOOL _waitForBeforeload;
4040
}
4141

src/ios/CDVUIInAppBrowser.m

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ - (void)pluginInitialize
5353
instance = self;
5454
_previousStatusBarStyle = -1;
5555
_callbackIdPattern = nil;
56-
_useBeforeload = NO;
56+
_beforeload = @"";
5757
_waitForBeforeload = NO;
5858
}
5959

@@ -219,8 +219,12 @@ - (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
219219
}
220220

221221
// use of beforeload event
222-
_useBeforeload = browserOptions.beforeload;
223-
_waitForBeforeload = browserOptions.beforeload;
222+
if([browserOptions.beforeload isKindOfClass:[NSString class]]){
223+
_beforeload = browserOptions.beforeload;
224+
}else{
225+
_beforeload = @"yes";
226+
}
227+
_waitForBeforeload = ![_beforeload isEqualToString:@""];
224228

225229
[self.inAppBrowserViewController navigateTo:url];
226230
if (!browserOptions.hidden) {
@@ -320,7 +324,7 @@ - (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command
320324
{
321325
NSString* urlStr = [command argumentAtIndex:0];
322326

323-
if (!_useBeforeload) {
327+
if ([_beforeload isEqualToString:@""]) {
324328
NSLog(@"unexpected loadAfterBeforeload called without feature beforeload=yes");
325329
}
326330
if (self.inAppBrowserViewController == nil) {
@@ -454,6 +458,22 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*
454458
NSURL* url = request.URL;
455459
BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
456460
BOOL shouldStart = YES;
461+
BOOL useBeforeLoad = NO;
462+
NSString* httpMethod = request.HTTPMethod;
463+
NSString* errorMessage = nil;
464+
465+
if([_beforeload isEqualToString:@"post"]){
466+
//TODO handle POST requests by preserving POST data then remove this condition
467+
errorMessage = @"beforeload doesn't yet support POST requests";
468+
}
469+
else if(isTopLevelNavigation && (
470+
[_beforeload isEqualToString:@"yes"]
471+
|| ([_beforeload isEqualToString:@"get"] && [httpMethod isEqualToString:@"GET"])
472+
// TODO comment in when POST requests are handled
473+
// || ([_beforeload isEqualToString:@"post"] && [httpMethod isEqualToString:@"POST"])
474+
)){
475+
useBeforeLoad = YES;
476+
}
457477

458478
// See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
459479
// and the path, if present, should be a JSON-encoded value to pass to the callback.
@@ -498,15 +518,23 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*
498518
}
499519
}
500520

501-
// When beforeload=yes, on first URL change, initiate JS callback. Only after the beforeload event, continue.
502-
if (_waitForBeforeload && isTopLevelNavigation) {
521+
// When beforeload, on first URL change, initiate JS callback. Only after the beforeload event, continue.
522+
if (_waitForBeforeload && useBeforeLoad) {
503523
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
504524
messageAsDictionary:@{@"type":@"beforeload", @"url":[url absoluteString]}];
505525
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
506-
526+
507527
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
508528
return NO;
509529
}
530+
531+
if(errorMessage != nil){
532+
NSLog(errorMessage);
533+
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
534+
messageAsDictionary:@{@"type":@"loaderror", @"url":[url absoluteString], @"code": @"-1", @"message": errorMessage}];
535+
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
536+
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
537+
}
510538

511539
//if is an app store link, let the system handle it, otherwise it fails to load it
512540
if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) {
@@ -523,7 +551,7 @@ - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*
523551
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
524552
}
525553

526-
if (_useBeforeload && isTopLevelNavigation) {
554+
if (useBeforeLoad) {
527555
_waitForBeforeload = YES;
528556
}
529557

src/ios/CDVWKInAppBrowser.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
@interface CDVWKInAppBrowser : CDVPlugin {
3030
@private
31-
BOOL _useBeforeload;
31+
NSString* _beforeload;
3232
BOOL _waitForBeforeload;
3333
}
3434

0 commit comments

Comments
 (0)