android: allow whitelisting urls to bypass default webview loading

Summary:
This is a workaround for missing PDF url support in Android WebView, which is a general known issue: when tapping a PDF url within WebView, instead of doing nothing, we just let android default intent handle it (e.g. it will open Chrome to load it).

This is basically to trick `shouldOverrideUrlLoading()` to return true for the specific url. The drawback is that product code needs to provide the whitelist.

The proper fix would be to use PdfRenderer in that method, but it seems like it's only for API >= 21...

Differential Revision: D5619383

fbshipit-source-id: f86b930f970dab9a5f57999df69ce94b9508edc9
This commit is contained in:
Kevin Gozali 2017-08-16 19:03:25 -07:00 committed by Facebook Github Bot
parent 1cc7ae2ae1
commit 40a2885847
2 changed files with 54 additions and 4 deletions

View File

@ -195,6 +195,15 @@ class WebView extends React.Component {
* @platform android
*/
saveFormDataDisabled: PropTypes.bool,
/**
* Used on Android only, controls whether the given list of URL prefixes should
* make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
* default activity intent for those URL instead of loading it within the webview.
* Use this to list URLs that WebView cannot handle, e.g. a PDF url.
* @platform android
*/
urlPrefixesForDefaultIntent: PropTypes.arrayOf(PropTypes.string),
};
static defaultProps = {
@ -276,6 +285,7 @@ class WebView extends React.Component {
allowUniversalAccessFromFileURLs={this.props.allowUniversalAccessFromFileURLs}
mixedContentMode={this.props.mixedContentMode}
saveFormDataDisabled={this.props.saveFormDataDisabled}
urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
/>;
return (

View File

@ -12,6 +12,7 @@ package com.facebook.react.views.webview;
import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@ -110,6 +111,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
protected static class ReactWebViewClient extends WebViewClient {
protected boolean mLastLoadFailed = false;
protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
@Override
public void onPageFinished(WebView webView, String url) {
@ -137,8 +139,21 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("http://") || url.startsWith("https://") ||
url.startsWith("file://") || url.equals("about:blank")) {
boolean useDefaultIntent = false;
if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent.size() > 0) {
ArrayList<Object> urlPrefixesForDefaultIntent =
mUrlPrefixesForDefaultIntent.toArrayList();
for (Object urlPrefix : urlPrefixesForDefaultIntent) {
if (url.startsWith((String) urlPrefix)) {
useDefaultIntent = true;
break;
}
}
}
if (!useDefaultIntent &&
(url.startsWith("http://") || url.startsWith("https://") ||
url.startsWith("file://") || url.equals("about:blank"))) {
return false;
} else {
try {
@ -205,6 +220,10 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
event.putBoolean("canGoForward", webView.canGoForward());
return event;
}
public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
mUrlPrefixesForDefaultIntent = specialUrls;
}
}
/**
@ -214,6 +233,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
protected static class ReactWebView extends WebView implements LifecycleEventListener {
protected @Nullable String injectedJS;
protected boolean messagingEnabled = false;
protected @Nullable ReactWebViewClient mReactWebViewClient;
protected class ReactWebViewBridge {
ReactWebView mContext;
@ -254,6 +274,16 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
cleanupCallbacksAndDestroy();
}
@Override
public void setWebViewClient(WebViewClient client) {
super.setWebViewClient(client);
mReactWebViewClient = (ReactWebViewClient)client;
}
public @Nullable ReactWebViewClient getReactWebViewClient() {
return mReactWebViewClient;
}
public void setInjectedJavaScript(@Nullable String js) {
injectedJS = js;
}
@ -413,7 +443,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
}
@ReactProp(name = "saveFormDataDisabled")
public void setSaveFormDataDisabled(WebView view, boolean disable) {
view.getSettings().setSaveFormData(!disable);
@ -507,7 +537,17 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if ("compatibility".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
}
}
}
}
@ReactProp(name = "urlPrefixesForDefaultIntent")
public void setUrlPrefixesForDefaultIntent(
WebView view,
@Nullable ReadableArray urlPrefixesForDefaultIntent) {
ReactWebViewClient client = ((ReactWebView) view).getReactWebViewClient();
if (client != null && urlPrefixesForDefaultIntent != null) {
client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
}
}