2018-08-15 20:30:06 -07:00
package com.reactnativecommunity.webview ;
2018-07-30 20:21:48 -07:00
2019-01-30 09:32:46 +00:00
import android.annotation.SuppressLint ;
2018-07-30 20:21:48 -07:00
import android.annotation.TargetApi ;
2019-01-07 06:02:47 -08:00
import android.app.DownloadManager ;
2018-07-30 20:21:48 -07:00
import android.content.Context ;
2019-08-29 09:38:25 -05:00
import android.content.pm.ActivityInfo ;
2019-08-02 12:23:59 -03:00
import android.content.pm.PackageManager ;
2018-07-30 20:21:48 -07:00
import android.graphics.Bitmap ;
2019-05-16 15:22:08 -07:00
import android.graphics.Color ;
2019-08-02 12:23:59 -03:00
import android.Manifest ;
2020-06-13 17:24:56 -04:00
import android.net.http.SslError ;
2018-07-30 20:21:48 -07:00
import android.net.Uri ;
import android.os.Build ;
2019-01-07 06:02:47 -08:00
import android.os.Environment ;
2020-08-25 23:59:51 -07:00
import android.os.SystemClock ;
2018-07-30 20:21:48 -07:00
import android.text.TextUtils ;
2020-08-06 17:21:01 -03:00
import android.util.Log ;
2019-05-16 15:22:08 -07:00
import android.view.Gravity ;
2018-10-17 16:48:19 +02:00
import android.view.View ;
2019-05-16 15:22:08 -07:00
import android.view.ViewGroup ;
2018-07-30 20:21:48 -07:00
import android.view.ViewGroup.LayoutParams ;
2019-05-16 15:22:08 -07:00
import android.view.WindowManager ;
2018-07-30 20:21:48 -07:00
import android.webkit.ConsoleMessage ;
import android.webkit.CookieManager ;
2019-01-07 06:02:47 -08:00
import android.webkit.DownloadListener ;
2018-07-30 20:21:48 -07:00
import android.webkit.GeolocationPermissions ;
import android.webkit.JavascriptInterface ;
2020-08-06 17:21:01 -03:00
import android.webkit.RenderProcessGoneDetail ;
2020-06-13 17:24:56 -04:00
import android.webkit.SslErrorHandler ;
2019-08-02 12:23:59 -03:00
import android.webkit.PermissionRequest ;
2019-01-07 06:02:47 -08:00
import android.webkit.URLUtil ;
2018-07-30 20:21:48 -07:00
import android.webkit.ValueCallback ;
import android.webkit.WebChromeClient ;
2018-11-30 02:59:12 +01:00
import android.webkit.WebResourceRequest ;
2019-09-20 17:43:28 -07:00
import android.webkit.WebResourceResponse ;
2018-07-30 20:21:48 -07:00
import android.webkit.WebSettings ;
import android.webkit.WebView ;
import android.webkit.WebViewClient ;
2019-05-16 15:22:08 -07:00
import android.widget.FrameLayout ;
2019-01-07 06:02:47 -08:00
2020-08-25 23:59:51 -07:00
import androidx.annotation.Nullable ;
import androidx.annotation.RequiresApi ;
import androidx.core.content.ContextCompat ;
import androidx.core.util.Pair ;
import com.facebook.common.logging.FLog ;
2019-05-16 18:27:16 -04:00
import com.facebook.react.views.scroll.ScrollEvent ;
import com.facebook.react.views.scroll.ScrollEventType ;
import com.facebook.react.views.scroll.OnScrollDispatchHelper ;
2018-07-30 20:21:48 -07:00
import com.facebook.react.bridge.Arguments ;
2020-04-06 20:25:28 +03:00
import com.facebook.react.bridge.CatalystInstance ;
2018-07-30 20:21:48 -07:00
import com.facebook.react.bridge.LifecycleEventListener ;
import com.facebook.react.bridge.ReactContext ;
import com.facebook.react.bridge.ReadableArray ;
import com.facebook.react.bridge.ReadableMap ;
import com.facebook.react.bridge.ReadableMapKeySetIterator ;
import com.facebook.react.bridge.WritableMap ;
2020-04-06 20:25:28 +03:00
import com.facebook.react.bridge.WritableNativeArray ;
import com.facebook.react.bridge.WritableNativeMap ;
2018-07-30 20:21:48 -07:00
import com.facebook.react.common.MapBuilder ;
import com.facebook.react.common.build.ReactBuildConfig ;
import com.facebook.react.module.annotations.ReactModule ;
import com.facebook.react.uimanager.SimpleViewManager ;
import com.facebook.react.uimanager.ThemedReactContext ;
2019-03-19 15:42:47 +01:00
import com.facebook.react.uimanager.UIManagerModule ;
2018-07-30 20:21:48 -07:00
import com.facebook.react.uimanager.annotations.ReactProp ;
import com.facebook.react.uimanager.events.ContentSizeChangeEvent ;
import com.facebook.react.uimanager.events.Event ;
import com.facebook.react.uimanager.events.EventDispatcher ;
2020-08-25 23:59:51 -07:00
import com.reactnativecommunity.webview.RNCWebViewModule.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState ;
2018-08-15 20:30:06 -07:00
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent ;
2019-09-20 17:43:28 -07:00
import com.reactnativecommunity.webview.events.TopHttpErrorEvent ;
2018-08-15 20:30:06 -07:00
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent ;
2019-03-19 15:42:47 +01:00
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent ;
2018-08-15 20:30:06 -07:00
import com.reactnativecommunity.webview.events.TopLoadingStartEvent ;
import com.reactnativecommunity.webview.events.TopMessageEvent ;
2018-11-30 02:59:12 +01:00
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent ;
2020-08-06 17:21:01 -03:00
import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent ;
2019-03-19 15:42:47 +01:00
2018-07-30 20:21:48 -07:00
import org.json.JSONException ;
import org.json.JSONObject ;
2019-03-19 15:42:47 +01:00
import java.io.UnsupportedEncodingException ;
import java.net.MalformedURLException ;
import java.net.URL ;
import java.net.URLEncoder ;
2019-08-02 12:23:59 -03:00
import java.util.ArrayList ;
2019-03-19 15:42:47 +01:00
import java.util.HashMap ;
import java.util.Locale ;
import java.util.Map ;
2020-08-25 23:59:51 -07:00
import java.util.concurrent.atomic.AtomicReference ;
2019-03-19 15:42:47 +01:00
2018-07-30 20:21:48 -07:00
/**
* Manages instances of {@link WebView}
2019-03-19 15:42:47 +01:00
* <p>
2018-07-30 20:21:48 -07:00
* Can accept following commands:
2019-03-19 15:42:47 +01:00
* - GO_BACK
* - GO_FORWARD
* - RELOAD
* - LOAD_URL
* <p>
2018-07-30 20:21:48 -07:00
* {@link WebView} instances could emit following direct events:
2019-03-19 15:42:47 +01:00
* - topLoadingFinish
* - topLoadingStart
* - topLoadingStart
* - topLoadingProgress
* - topShouldStartLoadWithRequest
* <p>
2018-07-30 20:21:48 -07:00
* Each event will carry the following properties:
2019-03-19 15:42:47 +01:00
* - target - view's react tag
* - url - url set for the webview
* - loading - whether webview is in a loading state
* - title - title of the current page
* - canGoBack - boolean, whether there is anything on a history stack to go back
* - canGoForward - boolean, whether it is possible to request GO_FORWARD command
2018-07-30 20:21:48 -07:00
*/
2018-09-19 01:19:06 +02:00
@ReactModule ( name = RNCWebViewManager . REACT_CLASS )
public class RNCWebViewManager extends SimpleViewManager < WebView > {
2020-08-25 23:59:51 -07:00
private static final String TAG = "RNCWebViewManager" ;
2018-07-30 20:21:48 -07:00
public static final int COMMAND_GO_BACK = 1 ;
public static final int COMMAND_GO_FORWARD = 2 ;
public static final int COMMAND_RELOAD = 3 ;
public static final int COMMAND_STOP_LOADING = 4 ;
public static final int COMMAND_POST_MESSAGE = 5 ;
public static final int COMMAND_INJECT_JAVASCRIPT = 6 ;
2018-11-30 02:59:12 +01:00
public static final int COMMAND_LOAD_URL = 7 ;
2019-08-06 11:56:59 +03:00
public static final int COMMAND_FOCUS = 8 ;
2019-11-12 12:09:16 +03:00
// android commands
public static final int COMMAND_CLEAR_FORM_DATA = 1000 ;
public static final int COMMAND_CLEAR_CACHE = 1001 ;
public static final int COMMAND_CLEAR_HISTORY = 1002 ;
2019-03-19 15:42:47 +01:00
protected static final String REACT_CLASS = "RNCWebView" ;
protected static final String HTML_ENCODING = "UTF-8" ;
protected static final String HTML_MIME_TYPE = "text/html" ;
protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView" ;
protected static final String HTTP_METHOD_POST = "POST" ;
2018-07-30 20:21:48 -07:00
// Use `webView.loadUrl("about:blank")` to reliably reset the view
// state and release page resources (including any running JavaScript).
protected static final String BLANK_URL = "about:blank" ;
2020-08-25 23:59:51 -07:00
protected static final int SHOULD_OVERRIDE_URL_LOADING_TIMEOUT = 250 ;
2018-07-30 20:21:48 -07:00
protected WebViewConfig mWebViewConfig ;
2019-05-16 15:22:08 -07:00
protected RNCWebChromeClient mWebChromeClient = null ;
protected boolean mAllowsFullscreenVideo = false ;
2019-08-02 04:46:03 -04:00
protected @Nullable String mUserAgent = null ;
protected @Nullable String mUserAgentWithApplicationName = null ;
2019-05-16 15:22:08 -07:00
2018-09-19 01:19:06 +02:00
public RNCWebViewManager () {
2018-07-30 20:21:48 -07:00
mWebViewConfig = new WebViewConfig () {
public void configWebView ( WebView webView ) {
}
};
}
2018-09-19 01:19:06 +02:00
public RNCWebViewManager ( WebViewConfig webViewConfig ) {
2018-07-30 20:21:48 -07:00
mWebViewConfig = webViewConfig ;
}
2019-03-19 15:42:47 +01:00
protected static void dispatchEvent ( WebView webView , Event event ) {
ReactContext reactContext = ( ReactContext ) webView . getContext ();
EventDispatcher eventDispatcher =
reactContext . getNativeModule ( UIManagerModule . class ). getEventDispatcher ();
eventDispatcher . dispatchEvent ( event );
}
2018-07-30 20:21:48 -07:00
@Override
public String getName () {
return REACT_CLASS ;
}
2018-09-19 01:19:06 +02:00
protected RNCWebView createRNCWebViewInstance ( ThemedReactContext reactContext ) {
return new RNCWebView ( reactContext );
2018-07-30 20:21:48 -07:00
}
@Override
@TargetApi ( Build . VERSION_CODES . LOLLIPOP )
protected WebView createViewInstance ( ThemedReactContext reactContext ) {
2018-09-19 01:19:06 +02:00
RNCWebView webView = createRNCWebViewInstance ( reactContext );
2019-05-16 15:22:08 -07:00
setupWebChromeClient ( reactContext , webView );
2018-07-30 20:21:48 -07:00
reactContext . addLifecycleEventListener ( webView );
mWebViewConfig . configWebView ( webView );
WebSettings settings = webView . getSettings ();
settings . setBuiltInZoomControls ( true );
settings . setDisplayZoomControls ( false );
settings . setDomStorageEnabled ( true );
settings . setAllowFileAccess ( false );
settings . setAllowContentAccess ( false );
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . JELLY_BEAN ) {
settings . setAllowFileAccessFromFileURLs ( false );
setAllowUniversalAccessFromFileURLs ( webView , false );
}
setMixedContentMode ( webView , "never" );
// Fixes broken full-screen modals/galleries due to body height being 0.
webView . setLayoutParams (
2019-03-19 15:42:47 +01:00
new LayoutParams ( LayoutParams . MATCH_PARENT ,
LayoutParams . MATCH_PARENT ));
2018-07-30 20:21:48 -07:00
if ( ReactBuildConfig . DEBUG && Build . VERSION . SDK_INT >= Build . VERSION_CODES . KITKAT ) {
WebView . setWebContentsDebuggingEnabled ( true );
}
2019-01-07 06:02:47 -08:00
webView . setDownloadListener ( new DownloadListener () {
public void onDownloadStart ( String url , String userAgent , String contentDisposition , String mimetype , long contentLength ) {
2020-04-10 13:41:53 -07:00
webView . setIgnoreErrFailedForThisURL ( url );
2019-03-20 14:04:47 +01:00
RNCWebViewModule module = getModule ( reactContext );
2019-01-07 06:02:47 -08:00
DownloadManager . Request request = new DownloadManager . Request ( Uri . parse ( url ));
2019-01-29 10:05:25 +01:00
String fileName = URLUtil . guessFileName ( url , contentDisposition , mimetype );
2019-01-07 06:02:47 -08:00
String downloadMessage = "Downloading " + fileName ;
//Attempt to add cookie, if it exists
URL urlObj = null ;
2019-01-11 10:44:42 +01:00
try {
2019-01-07 06:02:47 -08:00
urlObj = new URL ( url );
String baseUrl = urlObj . getProtocol () + "://" + urlObj . getHost ();
2019-01-11 10:44:42 +01:00
String cookie = CookieManager . getInstance (). getCookie ( baseUrl );
2019-01-07 06:02:47 -08:00
request . addRequestHeader ( "Cookie" , cookie );
} catch ( MalformedURLException e ) {
System . out . println ( "Error getting cookie for DownloadManager: " + e . toString ());
2019-01-11 10:44:42 +01:00
e . printStackTrace ();
2019-01-07 06:02:47 -08:00
}
//Finish setting up request
request . addRequestHeader ( "User-Agent" , userAgent );
request . setTitle ( fileName );
request . setDescription ( downloadMessage );
request . allowScanningByMediaScanner ();
request . setNotificationVisibility ( DownloadManager . Request . VISIBILITY_VISIBLE_NOTIFY_COMPLETED );
request . setDestinationInExternalPublicDir ( Environment . DIRECTORY_DOWNLOADS , fileName );
module . setDownloadRequest ( request );
if ( module . grantFileDownloaderPermissions ()) {
module . downloadFile ();
}
}
});
2018-07-30 20:21:48 -07:00
return webView ;
}
@ReactProp ( name = "javaScriptEnabled" )
public void setJavaScriptEnabled ( WebView view , boolean enabled ) {
view . getSettings (). setJavaScriptEnabled ( enabled );
}
2019-02-12 07:47:24 -06:00
@ReactProp ( name = "showsHorizontalScrollIndicator" )
public void setShowsHorizontalScrollIndicator ( WebView view , boolean enabled ) {
view . setHorizontalScrollBarEnabled ( enabled );
}
@ReactProp ( name = "showsVerticalScrollIndicator" )
public void setShowsVerticalScrollIndicator ( WebView view , boolean enabled ) {
view . setVerticalScrollBarEnabled ( enabled );
}
2019-03-19 15:42:47 +01:00
2019-01-30 09:32:46 +00:00
@ReactProp ( name = "cacheEnabled" )
public void setCacheEnabled ( WebView view , boolean enabled ) {
if ( enabled ) {
Context ctx = view . getContext ();
if ( ctx != null ) {
view . getSettings (). setAppCachePath ( ctx . getCacheDir (). getAbsolutePath ());
view . getSettings (). setCacheMode ( WebSettings . LOAD_DEFAULT );
view . getSettings (). setAppCacheEnabled ( true );
}
} else {
view . getSettings (). setCacheMode ( WebSettings . LOAD_NO_CACHE );
view . getSettings (). setAppCacheEnabled ( false );
}
}
2019-09-29 23:45:20 +10:00
@ReactProp ( name = "cacheMode" )
public void setCacheMode ( WebView view , String cacheModeString ) {
Integer cacheMode ;
switch ( cacheModeString ) {
case "LOAD_CACHE_ONLY" :
cacheMode = WebSettings . LOAD_CACHE_ONLY ;
break ;
case "LOAD_CACHE_ELSE_NETWORK" :
cacheMode = WebSettings . LOAD_CACHE_ELSE_NETWORK ;
2019-11-12 12:09:16 +03:00
break ;
2019-09-29 23:45:20 +10:00
case "LOAD_NO_CACHE" :
cacheMode = WebSettings . LOAD_NO_CACHE ;
break ;
case "LOAD_DEFAULT" :
default :
cacheMode = WebSettings . LOAD_DEFAULT ;
break ;
}
view . getSettings (). setCacheMode ( cacheMode );
}
2019-01-22 17:21:10 +08:00
@ReactProp ( name = "androidHardwareAccelerationDisabled" )
public void setHardwareAccelerationDisabled ( WebView view , boolean disabled ) {
2019-10-22 14:56:50 +07:00
if ( disabled ) {
2019-01-22 17:21:10 +08:00
view . setLayerType ( View . LAYER_TYPE_SOFTWARE , null );
}
}
2020-08-24 14:23:38 +02:00
@ReactProp ( name = "androidLayerType" )
public void setLayerType ( WebView view , String layerTypeString ) {
int layerType = View . LAYER_TYPE_NONE ;
switch ( layerTypeString ) {
case "hardware" :
layerType = View . LAYER_TYPE_HARDWARE ;
break ;
case "software" :
layerType = View . LAYER_TYPE_SOFTWARE ;
break ;
}
view . setLayerType ( layerType , null );
}
2018-10-17 16:48:19 +02:00
@ReactProp ( name = "overScrollMode" )
public void setOverScrollMode ( WebView view , String overScrollModeString ) {
Integer overScrollMode ;
switch ( overScrollModeString ) {
case "never" :
overScrollMode = View . OVER_SCROLL_NEVER ;
break ;
case "content" :
overScrollMode = View . OVER_SCROLL_IF_CONTENT_SCROLLS ;
break ;
case "always" :
default :
overScrollMode = View . OVER_SCROLL_ALWAYS ;
break ;
}
view . setOverScrollMode ( overScrollMode );
}
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "thirdPartyCookiesEnabled" )
public void setThirdPartyCookiesEnabled ( WebView view , boolean enabled ) {
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . LOLLIPOP ) {
CookieManager . getInstance (). setAcceptThirdPartyCookies ( view , enabled );
}
}
2019-03-28 11:52:18 +03:00
@ReactProp ( name = "textZoom" )
public void setTextZoom ( WebView view , int value ) {
view . getSettings (). setTextZoom ( value );
}
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "scalesPageToFit" )
public void setScalesPageToFit ( WebView view , boolean enabled ) {
2019-03-01 05:02:44 +08:00
view . getSettings (). setLoadWithOverviewMode ( enabled );
view . getSettings (). setUseWideViewPort ( enabled );
2018-07-30 20:21:48 -07:00
}
@ReactProp ( name = "domStorageEnabled" )
public void setDomStorageEnabled ( WebView view , boolean enabled ) {
view . getSettings (). setDomStorageEnabled ( enabled );
}
@ReactProp ( name = "userAgent" )
public void setUserAgent ( WebView view , @Nullable String userAgent ) {
if ( userAgent != null ) {
2019-08-02 04:46:03 -04:00
mUserAgent = userAgent ;
} else {
mUserAgent = null ;
}
this . setUserAgentString ( view );
}
@ReactProp ( name = "applicationNameForUserAgent" )
public void setApplicationNameForUserAgent ( WebView view , @Nullable String applicationName ) {
if ( applicationName != null ) {
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . JELLY_BEAN_MR1 ) {
String defaultUserAgent = WebSettings . getDefaultUserAgent ( view . getContext ());
mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName ;
}
} else {
mUserAgentWithApplicationName = null ;
}
this . setUserAgentString ( view );
}
protected void setUserAgentString ( WebView view ) {
if ( mUserAgent != null ) {
view . getSettings (). setUserAgentString ( mUserAgent );
} else if ( mUserAgentWithApplicationName != null ) {
view . getSettings (). setUserAgentString ( mUserAgentWithApplicationName );
} else if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . JELLY_BEAN_MR1 ) {
// handle unsets of `userAgent` prop as long as device is >= API 17
view . getSettings (). setUserAgentString ( WebSettings . getDefaultUserAgent ( view . getContext ()));
2018-07-30 20:21:48 -07:00
}
}
2018-11-19 11:25:28 +01:00
@TargetApi ( Build . VERSION_CODES . JELLY_BEAN_MR1 )
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "mediaPlaybackRequiresUserAction" )
public void setMediaPlaybackRequiresUserAction ( WebView view , boolean requires ) {
view . getSettings (). setMediaPlaybackRequiresUserGesture ( requires );
}
2020-05-28 09:54:20 -07:00
@ReactProp ( name = "javaScriptCanOpenWindowsAutomatically" )
public void setJavaScriptCanOpenWindowsAutomatically ( WebView view , boolean enabled ) {
view . getSettings (). setJavaScriptCanOpenWindowsAutomatically ( enabled );
}
2019-09-29 15:50:39 +02:00
@ReactProp ( name = "allowFileAccessFromFileURLs" )
public void setAllowFileAccessFromFileURLs ( WebView view , boolean allow ) {
view . getSettings (). setAllowFileAccessFromFileURLs ( allow );
}
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "allowUniversalAccessFromFileURLs" )
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 );
}
@ReactProp ( name = "injectedJavaScript" )
public void setInjectedJavaScript ( WebView view , @Nullable String injectedJavaScript ) {
2018-09-19 01:19:06 +02:00
(( RNCWebView ) view ). setInjectedJavaScript ( injectedJavaScript );
2018-07-30 20:21:48 -07:00
}
2020-06-13 16:54:48 -04:00
@ReactProp ( name = "injectedJavaScriptBeforeContentLoaded" )
public void setInjectedJavaScriptBeforeContentLoaded ( WebView view , @Nullable String injectedJavaScriptBeforeContentLoaded ) {
(( RNCWebView ) view ). setInjectedJavaScriptBeforeContentLoaded ( injectedJavaScriptBeforeContentLoaded );
}
@ReactProp ( name = "injectedJavaScriptForMainFrameOnly" )
public void setInjectedJavaScriptForMainFrameOnly ( WebView view , boolean enabled ) {
(( RNCWebView ) view ). setInjectedJavaScriptForMainFrameOnly ( enabled );
}
@ReactProp ( name = "injectedJavaScriptBeforeContentLoadedForMainFrameOnly" )
public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly ( WebView view , boolean enabled ) {
(( RNCWebView ) view ). setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly ( enabled );
}
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "messagingEnabled" )
public void setMessagingEnabled ( WebView view , boolean enabled ) {
2018-09-19 01:19:06 +02:00
(( RNCWebView ) view ). setMessagingEnabled ( enabled );
2018-07-30 20:21:48 -07:00
}
2020-04-09 18:05:44 +03:00
@ReactProp ( name = "messagingModuleName" )
public void setMessagingModuleName ( WebView view , String moduleName ) {
(( RNCWebView ) view ). setMessagingModuleName ( moduleName );
}
2020-05-29 18:21:24 +02:00
2019-08-02 00:48:54 -07:00
@ReactProp ( name = "incognito" )
public void setIncognito ( WebView view , boolean enabled ) {
2020-08-15 05:04:20 +02:00
// Don't do anything when incognito is disabled
if ( ! enabled ) {
return ;
}
2019-08-02 00:48:54 -07:00
// Remove all previous cookies
2019-08-29 22:47:48 +08:00
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . LOLLIPOP ) {
CookieManager . getInstance (). removeAllCookies ( null );
} else {
CookieManager . getInstance (). removeAllCookie ();
}
2019-08-02 00:48:54 -07:00
// Disable caching
view . getSettings (). setCacheMode ( WebSettings . LOAD_NO_CACHE );
2020-08-15 05:04:20 +02:00
view . getSettings (). setAppCacheEnabled ( false );
2019-08-02 00:48:54 -07:00
view . clearHistory ();
2020-08-15 05:04:20 +02:00
view . clearCache ( true );
2019-08-02 00:48:54 -07:00
// No form data or autofill enabled
view . clearFormData ();
2020-08-15 05:04:20 +02:00
view . getSettings (). setSavePassword ( false );
view . getSettings (). setSaveFormData ( false );
2019-08-02 00:48:54 -07:00
}
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "source" )
public void setSource ( WebView view , @Nullable ReadableMap source ) {
if ( source != null ) {
if ( source . hasKey ( "html" )) {
String html = source . getString ( "html" );
2019-04-15 17:56:45 +10:00
String baseUrl = source . hasKey ( "baseUrl" ) ? source . getString ( "baseUrl" ) : "" ;
view . loadDataWithBaseURL ( baseUrl , html , HTML_MIME_TYPE , HTML_ENCODING , null );
2018-07-30 20:21:48 -07:00
return ;
}
if ( source . hasKey ( "uri" )) {
String url = source . getString ( "uri" );
String previousUrl = view . getUrl ();
if ( previousUrl != null && previousUrl . equals ( url )) {
return ;
}
if ( source . hasKey ( "method" )) {
String method = source . getString ( "method" );
2018-10-13 00:40:20 +02:00
if ( method . equalsIgnoreCase ( HTTP_METHOD_POST )) {
2018-07-30 20:21:48 -07:00
byte [] postData = null ;
if ( source . hasKey ( "body" )) {
String body = source . getString ( "body" );
try {
postData = body . getBytes ( "UTF-8" );
} catch ( UnsupportedEncodingException e ) {
postData = body . getBytes ();
}
}
if ( postData == null ) {
postData = new byte [ 0 ] ;
}
view . postUrl ( url , postData );
return ;
}
}
HashMap < String , String > headerMap = new HashMap <> ();
if ( source . hasKey ( "headers" )) {
ReadableMap headers = source . getMap ( "headers" );
ReadableMapKeySetIterator iter = headers . keySetIterator ();
while ( iter . hasNextKey ()) {
String key = iter . nextKey ();
if ( "user-agent" . equals ( key . toLowerCase ( Locale . ENGLISH ))) {
if ( view . getSettings () != null ) {
view . getSettings (). setUserAgentString ( headers . getString ( key ));
}
} else {
headerMap . put ( key , headers . getString ( key ));
}
}
}
view . loadUrl ( url , headerMap );
return ;
}
}
view . loadUrl ( BLANK_URL );
}
@ReactProp ( name = "onContentSizeChange" )
public void setOnContentSizeChange ( WebView view , boolean sendContentSizeChangeEvents ) {
2019-01-10 10:38:05 +00:00
(( RNCWebView ) view ). setSendContentSizeChangeEvents ( sendContentSizeChangeEvents );
2018-07-30 20:21:48 -07:00
}
@ReactProp ( name = "mixedContentMode" )
public void setMixedContentMode ( WebView view , @Nullable String mixedContentMode ) {
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . LOLLIPOP ) {
if ( mixedContentMode == null || "never" . equals ( mixedContentMode )) {
view . getSettings (). setMixedContentMode ( WebSettings . MIXED_CONTENT_NEVER_ALLOW );
} else if ( "always" . equals ( mixedContentMode )) {
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 (
2019-03-19 15:42:47 +01:00
WebView view ,
@Nullable ReadableArray urlPrefixesForDefaultIntent ) {
2018-09-19 01:19:06 +02:00
RNCWebViewClient client = (( RNCWebView ) view ). getRNCWebViewClient ();
2018-07-30 20:21:48 -07:00
if ( client != null && urlPrefixesForDefaultIntent != null ) {
client . setUrlPrefixesForDefaultIntent ( urlPrefixesForDefaultIntent );
}
}
2019-05-16 15:22:08 -07:00
@ReactProp ( name = "allowsFullscreenVideo" )
public void setAllowsFullscreenVideo (
WebView view ,
@Nullable Boolean allowsFullscreenVideo ) {
mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo ;
setupWebChromeClient (( ReactContext ) view . getContext (), view );
}
2018-09-14 01:09:39 +02:00
@ReactProp ( name = "allowFileAccess" )
public void setAllowFileAccess (
WebView view ,
@Nullable Boolean allowFileAccess ) {
view . getSettings (). setAllowFileAccess ( allowFileAccess != null && allowFileAccess );
}
2018-07-30 20:21:48 -07:00
@ReactProp ( name = "geolocationEnabled" )
public void setGeolocationEnabled (
WebView view ,
@Nullable Boolean isGeolocationEnabled ) {
view . getSettings (). setGeolocationEnabled ( isGeolocationEnabled != null && isGeolocationEnabled );
}
2019-06-03 03:19:39 +09:00
@ReactProp ( name = "onScroll" )
public void setOnScroll ( WebView view , boolean hasScrollEvent ) {
(( RNCWebView ) view ). setHasScrollEvent ( hasScrollEvent );
}
2018-07-30 20:21:48 -07:00
@Override
protected void addEventEmitters ( ThemedReactContext reactContext , WebView view ) {
// Do not register default touch emitter and let WebView implementation handle touches
2018-09-19 01:19:06 +02:00
view . setWebViewClient ( new RNCWebViewClient ());
2018-07-30 20:21:48 -07:00
}
2018-10-17 21:08:52 +08:00
@Override
public Map getExportedCustomDirectEventTypeConstants () {
2018-11-30 02:59:12 +01:00
Map export = super . getExportedCustomDirectEventTypeConstants ();
if ( export == null ) {
export = MapBuilder . newHashMap ();
}
export . put ( TopLoadingProgressEvent . EVENT_NAME , MapBuilder . of ( "registrationName" , "onLoadingProgress" ));
export . put ( TopShouldStartLoadWithRequestEvent . EVENT_NAME , MapBuilder . of ( "registrationName" , "onShouldStartLoadWithRequest" ));
2019-05-17 15:25:53 +08:00
export . put ( ScrollEventType . getJSEventName ( ScrollEventType . SCROLL ), MapBuilder . of ( "registrationName" , "onScroll" ));
2019-09-20 17:43:28 -07:00
export . put ( TopHttpErrorEvent . EVENT_NAME , MapBuilder . of ( "registrationName" , "onHttpError" ));
2020-08-06 17:21:01 -03:00
export . put ( TopRenderProcessGoneEvent . EVENT_NAME , MapBuilder . of ( "registrationName" , "onRenderProcessGone" ));
2018-11-30 02:59:12 +01:00
return export ;
2018-10-17 21:08:52 +08:00
}
2018-07-30 20:21:48 -07:00
@Override
2019-03-19 15:42:47 +01:00
public @Nullable
Map < String , Integer > getCommandsMap () {
2019-11-12 12:09:16 +03:00
return MapBuilder . < String , Integer > builder ()
. put ( "goBack" , COMMAND_GO_BACK )
. put ( "goForward" , COMMAND_GO_FORWARD )
. put ( "reload" , COMMAND_RELOAD )
. put ( "stopLoading" , COMMAND_STOP_LOADING )
. put ( "postMessage" , COMMAND_POST_MESSAGE )
. put ( "injectJavaScript" , COMMAND_INJECT_JAVASCRIPT )
. put ( "loadUrl" , COMMAND_LOAD_URL )
. put ( "requestFocus" , COMMAND_FOCUS )
. put ( "clearFormData" , COMMAND_CLEAR_FORM_DATA )
. put ( "clearCache" , COMMAND_CLEAR_CACHE )
. put ( "clearHistory" , COMMAND_CLEAR_HISTORY )
. build ();
2018-07-30 20:21:48 -07:00
}
@Override
public void receiveCommand ( WebView root , int commandId , @Nullable ReadableArray args ) {
switch ( commandId ) {
case COMMAND_GO_BACK :
root . goBack ();
break ;
case COMMAND_GO_FORWARD :
root . goForward ();
break ;
case COMMAND_RELOAD :
root . reload ();
break ;
case COMMAND_STOP_LOADING :
root . stopLoading ();
break ;
case COMMAND_POST_MESSAGE :
try {
2018-09-19 01:19:06 +02:00
RNCWebView reactWebView = ( RNCWebView ) root ;
2018-07-30 20:21:48 -07:00
JSONObject eventInitDict = new JSONObject ();
eventInitDict . put ( "data" , args . getString ( 0 ));
2018-09-14 00:36:29 +02:00
reactWebView . evaluateJavascriptWithFallback ( "(function () {" +
2018-07-30 20:21:48 -07:00
"var event;" +
"var data = " + eventInitDict . toString () + ";" +
"try {" +
2019-03-19 15:42:47 +01:00
"event = new MessageEvent('message', data);" +
2018-07-30 20:21:48 -07:00
"} catch (e) {" +
2019-03-19 15:42:47 +01:00
"event = document.createEvent('MessageEvent');" +
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
2018-07-30 20:21:48 -07:00
"}" +
"document.dispatchEvent(event);" +
2019-03-19 15:42:47 +01:00
"})();" );
2018-07-30 20:21:48 -07:00
} catch ( JSONException e ) {
throw new RuntimeException ( e );
}
break ;
case COMMAND_INJECT_JAVASCRIPT :
2018-09-19 01:19:06 +02:00
RNCWebView reactWebView = ( RNCWebView ) root ;
2018-09-14 00:36:29 +02:00
reactWebView . evaluateJavascriptWithFallback ( args . getString ( 0 ));
2018-07-30 20:21:48 -07:00
break ;
2018-11-30 02:59:12 +01:00
case COMMAND_LOAD_URL :
if ( args == null ) {
throw new RuntimeException ( "Arguments for loading an url are null!" );
}
2020-05-28 17:21:20 -07:00
(( RNCWebView ) root ). progressChangedFilter . setWaitingForCommandLoadUrl ( false );
2018-11-30 02:59:12 +01:00
root . loadUrl ( args . getString ( 0 ));
break ;
2019-08-06 11:56:59 +03:00
case COMMAND_FOCUS :
root . requestFocus ();
break ;
2019-11-12 12:09:16 +03:00
case COMMAND_CLEAR_FORM_DATA :
root . clearFormData ();
break ;
case COMMAND_CLEAR_CACHE :
boolean includeDiskFiles = args != null && args . getBoolean ( 0 );
root . clearCache ( includeDiskFiles );
break ;
case COMMAND_CLEAR_HISTORY :
root . clearHistory ();
break ;
2018-07-30 20:21:48 -07:00
}
}
@Override
public void onDropViewInstance ( WebView webView ) {
super . onDropViewInstance ( webView );
2018-09-19 01:19:06 +02:00
(( ThemedReactContext ) webView . getContext ()). removeLifecycleEventListener (( RNCWebView ) webView );
(( RNCWebView ) webView ). cleanupCallbacksAndDestroy ();
2020-11-06 15:05:18 -08:00
mWebChromeClient = null ;
2018-07-30 20:21:48 -07:00
}
2019-05-16 15:22:08 -07:00
public static RNCWebViewModule getModule ( ReactContext reactContext ) {
2019-03-20 14:04:47 +01:00
return reactContext . getNativeModule ( RNCWebViewModule . class );
2018-11-21 12:46:43 +02:00
}
2019-03-19 15:42:47 +01:00
2019-05-16 15:22:08 -07:00
protected void setupWebChromeClient ( ReactContext reactContext , WebView webView ) {
if ( mAllowsFullscreenVideo ) {
2019-08-29 09:38:25 -05:00
int initialRequestedOrientation = reactContext . getCurrentActivity (). getRequestedOrientation ();
2019-05-16 15:22:08 -07:00
mWebChromeClient = new RNCWebChromeClient ( reactContext , webView ) {
2019-11-26 11:40:38 +02:00
@Override
public Bitmap getDefaultVideoPoster () {
return Bitmap . createBitmap ( 50 , 50 , Bitmap . Config . ARGB_8888 );
}
2020-05-29 18:21:24 +02:00
2019-05-16 15:22:08 -07:00
@Override
public void onShowCustomView ( View view , CustomViewCallback callback ) {
if ( mVideoView != null ) {
callback . onCustomViewHidden ();
return ;
}
mVideoView = view ;
mCustomViewCallback = callback ;
2019-08-29 09:38:25 -05:00
mReactContext . getCurrentActivity (). setRequestedOrientation ( ActivityInfo . SCREEN_ORIENTATION_UNSPECIFIED );
2019-05-16 15:22:08 -07:00
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . KITKAT ) {
mVideoView . setSystemUiVisibility ( FULLSCREEN_SYSTEM_UI_VISIBILITY );
mReactContext . getCurrentActivity (). getWindow (). setFlags ( WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS , WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS );
}
mVideoView . setBackgroundColor ( Color . BLACK );
getRootView (). addView ( mVideoView , FULLSCREEN_LAYOUT_PARAMS );
mWebView . setVisibility ( View . GONE );
mReactContext . addLifecycleEventListener ( this );
}
@Override
public void onHideCustomView () {
if ( mVideoView == null ) {
return ;
}
mVideoView . setVisibility ( View . GONE );
getRootView (). removeView ( mVideoView );
mCustomViewCallback . onCustomViewHidden ();
mVideoView = null ;
mCustomViewCallback = null ;
mWebView . setVisibility ( View . VISIBLE );
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . KITKAT ) {
mReactContext . getCurrentActivity (). getWindow (). clearFlags ( WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS );
}
2019-08-29 09:38:25 -05:00
mReactContext . getCurrentActivity (). setRequestedOrientation ( initialRequestedOrientation );
2019-05-16 15:22:08 -07:00
mReactContext . removeLifecycleEventListener ( this );
}
};
webView . setWebChromeClient ( mWebChromeClient );
} else {
if ( mWebChromeClient != null ) {
mWebChromeClient . onHideCustomView ();
}
2019-11-26 11:40:38 +02:00
mWebChromeClient = new RNCWebChromeClient ( reactContext , webView ) {
@Override
public Bitmap getDefaultVideoPoster () {
return Bitmap . createBitmap ( 50 , 50 , Bitmap . Config . ARGB_8888 );
}
};
2019-05-16 15:22:08 -07:00
webView . setWebChromeClient ( mWebChromeClient );
}
}
2019-03-19 15:42:47 +01:00
protected static class RNCWebViewClient extends WebViewClient {
protected boolean mLastLoadFailed = false ;
protected @Nullable
ReadableArray mUrlPrefixesForDefaultIntent ;
2020-05-28 17:21:20 -07:00
protected RNCWebView . ProgressChangedFilter progressChangedFilter = null ;
2020-04-10 13:41:53 -07:00
protected @Nullable String ignoreErrFailedForThisURL = null ;
public void setIgnoreErrFailedForThisURL ( @Nullable String url ) {
ignoreErrFailedForThisURL = url ;
}
2019-03-19 15:42:47 +01:00
@Override
public void onPageFinished ( WebView webView , String url ) {
super . onPageFinished ( webView , url );
if ( ! mLastLoadFailed ) {
RNCWebView reactWebView = ( RNCWebView ) webView ;
reactWebView . callInjectedJavaScript ();
emitFinishEvent ( webView , url );
}
}
@Override
public void onPageStarted ( WebView webView , String url , Bitmap favicon ) {
super . onPageStarted ( webView , url , favicon );
mLastLoadFailed = false ;
2020-06-13 16:54:48 -04:00
RNCWebView reactWebView = ( RNCWebView ) webView ;
2020-08-06 17:21:01 -03:00
reactWebView . callInjectedJavaScriptBeforeContentLoaded ();
2020-06-13 16:54:48 -04:00
2019-03-19 15:42:47 +01:00
dispatchEvent (
webView ,
new TopLoadingStartEvent (
webView . getId (),
createWebViewEvent ( webView , url )));
}
@Override
public boolean shouldOverrideUrlLoading ( WebView view , String url ) {
2020-08-25 23:59:51 -07:00
final RNCWebView rncWebView = ( RNCWebView ) view ;
final boolean isJsDebugging = (( ReactContext ) view . getContext ()). getJavaScriptContextHolder (). get () == 0 ;
2019-03-19 15:42:47 +01:00
2020-08-25 23:59:51 -07:00
if ( ! isJsDebugging && rncWebView . mCatalystInstance != null ) {
final Pair < Integer , AtomicReference < ShouldOverrideCallbackState >> lock = RNCWebViewModule . shouldOverrideUrlLoadingLock . getNewLock ();
final int lockIdentifier = lock . first ;
final AtomicReference < ShouldOverrideCallbackState > lockObject = lock . second ;
final WritableMap event = createWebViewEvent ( view , url );
event . putInt ( "lockIdentifier" , lockIdentifier );
rncWebView . sendDirectMessage ( "onShouldStartLoadWithRequest" , event );
try {
assert lockObject != null ;
synchronized ( lockObject ) {
final long startTime = SystemClock . elapsedRealtime ();
while ( lockObject . get () == ShouldOverrideCallbackState . UNDECIDED ) {
if ( SystemClock . elapsedRealtime () - startTime > SHOULD_OVERRIDE_URL_LOADING_TIMEOUT ) {
FLog . w ( TAG , "Did not receive response to shouldOverrideUrlLoading in time, defaulting to allow loading." );
RNCWebViewModule . shouldOverrideUrlLoadingLock . removeLock ( lockIdentifier );
return false ;
}
lockObject . wait ( SHOULD_OVERRIDE_URL_LOADING_TIMEOUT );
}
}
} catch ( InterruptedException e ) {
FLog . e ( TAG , "shouldOverrideUrlLoading was interrupted while waiting for result." , e );
RNCWebViewModule . shouldOverrideUrlLoadingLock . removeLock ( lockIdentifier );
return false ;
}
final boolean shouldOverride = lockObject . get () == ShouldOverrideCallbackState . SHOULD_OVERRIDE ;
RNCWebViewModule . shouldOverrideUrlLoadingLock . removeLock ( lockIdentifier );
return shouldOverride ;
} else {
FLog . w ( TAG , "Couldn't use blocking synchronous call for onShouldStartLoadWithRequest due to debugging or missing Catalyst instance, falling back to old event-and-load." );
progressChangedFilter . setWaitingForCommandLoadUrl ( true );
dispatchEvent (
view ,
new TopShouldStartLoadWithRequestEvent (
view . getId (),
createWebViewEvent ( view , url )));
return true ;
}
}
2019-03-19 15:42:47 +01:00
@TargetApi ( Build . VERSION_CODES . N )
@Override
public boolean shouldOverrideUrlLoading ( WebView view , WebResourceRequest request ) {
final String url = request . getUrl (). toString ();
return this . shouldOverrideUrlLoading ( view , url );
}
2020-06-13 17:24:56 -04:00
@Override
public void onReceivedSslError ( final WebView webView , final SslErrorHandler handler , final SslError error ) {
2020-10-22 03:35:31 -04:00
// onReceivedSslError is called for most requests, per Android docs: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%2520android.webkit.SslErrorHandler,%2520android.net.http.SslError)
// WebView.getUrl() will return the top-level window URL.
// If a top-level navigation triggers this error handler, the top-level URL will be the failing URL (not the URL of the currently-rendered page).
// This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
String topWindowUrl = webView . getUrl ();
String failingUrl = error . getUrl ();
// Cancel request after obtaining top-level URL.
// If request is cancelled before obtaining top-level URL, undesired behavior may occur.
// Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
2020-06-13 17:24:56 -04:00
handler . cancel ();
2020-10-22 03:35:31 -04:00
if ( ! topWindowUrl . equalsIgnoreCase ( failingUrl )) {
// If error is not due to top-level navigation, then do not call onReceivedError()
Log . w ( "RNCWebViewManager" , "Resource blocked from loading due to SSL error. Blocked URL: " + failingUrl );
return ;
}
2020-06-13 17:24:56 -04:00
int code = error . getPrimaryError ();
String description = "" ;
String descriptionPrefix = "SSL error: " ;
// https://developer.android.com/reference/android/net/http/SslError.html
switch ( code ) {
case SslError . SSL_DATE_INVALID :
description = "The date of the certificate is invalid" ;
break ;
case SslError . SSL_EXPIRED :
description = "The certificate has expired" ;
break ;
case SslError . SSL_IDMISMATCH :
description = "Hostname mismatch" ;
break ;
case SslError . SSL_INVALID :
description = "A generic error occurred" ;
break ;
case SslError . SSL_NOTYETVALID :
description = "The certificate is not yet valid" ;
break ;
case SslError . SSL_UNTRUSTED :
description = "The certificate authority is not trusted" ;
break ;
2020-08-06 17:21:01 -03:00
default :
2020-06-13 17:24:56 -04:00
description = "Unknown SSL Error" ;
break ;
}
2020-08-06 17:21:01 -03:00
2020-06-13 17:24:56 -04:00
description = descriptionPrefix + description ;
this . onReceivedError (
webView ,
code ,
description ,
failingUrl
);
}
2020-08-06 17:21:01 -03:00
2019-03-19 15:42:47 +01:00
@Override
public void onReceivedError (
WebView webView ,
int errorCode ,
String description ,
String failingUrl ) {
2020-04-10 13:41:53 -07:00
if ( ignoreErrFailedForThisURL != null
&& failingUrl . equals ( ignoreErrFailedForThisURL )
&& errorCode == - 1
&& description . equals ( "net::ERR_FAILED" )) {
// This is a workaround for a bug in the WebView.
// See these chromium issues for more context:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1023678
// https://bugs.chromium.org/p/chromium/issues/detail?id=1050635
// This entire commit should be reverted once this bug is resolved in chromium.
setIgnoreErrFailedForThisURL ( null );
return ;
}
2019-03-19 15:42:47 +01:00
super . onReceivedError ( webView , errorCode , description , failingUrl );
mLastLoadFailed = true ;
// In case of an error JS side expect to get a finish event first, and then get an error event
// Android WebView does it in the opposite way, so we need to simulate that behavior
emitFinishEvent ( webView , failingUrl );
WritableMap eventData = createWebViewEvent ( webView , failingUrl );
eventData . putDouble ( "code" , errorCode );
eventData . putString ( "description" , description );
dispatchEvent (
webView ,
new TopLoadingErrorEvent ( webView . getId (), eventData ));
}
2019-09-20 17:43:28 -07:00
@RequiresApi ( api = Build . VERSION_CODES . M )
@Override
public void onReceivedHttpError (
WebView webView ,
WebResourceRequest request ,
WebResourceResponse errorResponse ) {
super . onReceivedHttpError ( webView , request , errorResponse );
if ( request . isForMainFrame ()) {
WritableMap eventData = createWebViewEvent ( webView , request . getUrl (). toString ());
eventData . putInt ( "statusCode" , errorResponse . getStatusCode ());
eventData . putString ( "description" , errorResponse . getReasonPhrase ());
dispatchEvent (
webView ,
new TopHttpErrorEvent ( webView . getId (), eventData ));
}
}
2020-08-06 17:21:01 -03:00
@TargetApi ( Build . VERSION_CODES . O )
@Override
public boolean onRenderProcessGone ( WebView webView , RenderProcessGoneDetail detail ) {
// WebViewClient.onRenderProcessGone was added in O.
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . O ) {
return false ;
}
super . onRenderProcessGone ( webView , detail );
if ( detail . didCrash ()){
Log . e ( "RNCWebViewManager" , "The WebView rendering process crashed." );
}
else {
Log . w ( "RNCWebViewManager" , "The WebView rendering process was killed by the system." );
}
// if webView is null, we cannot return any event
// since the view is already dead/disposed
// still prevent the app crash by returning true.
if ( webView == null ){
return true ;
}
WritableMap event = createWebViewEvent ( webView , webView . getUrl ());
event . putBoolean ( "didCrash" , detail . didCrash ());
dispatchEvent (
webView ,
new TopRenderProcessGoneEvent ( webView . getId (), event )
);
// returning false would crash the app.
return true ;
}
2019-03-19 15:42:47 +01:00
protected void emitFinishEvent ( WebView webView , String url ) {
dispatchEvent (
webView ,
new TopLoadingFinishEvent (
webView . getId (),
createWebViewEvent ( webView , url )));
}
protected WritableMap createWebViewEvent ( WebView webView , String url ) {
WritableMap event = Arguments . createMap ();
event . putDouble ( "target" , webView . getId ());
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
// like onPageFinished
event . putString ( "url" , url );
event . putBoolean ( "loading" , ! mLastLoadFailed && webView . getProgress () != 100 );
event . putString ( "title" , webView . getTitle ());
event . putBoolean ( "canGoBack" , webView . canGoBack ());
event . putBoolean ( "canGoForward" , webView . canGoForward ());
return event ;
}
public void setUrlPrefixesForDefaultIntent ( ReadableArray specialUrls ) {
mUrlPrefixesForDefaultIntent = specialUrls ;
}
2020-05-28 17:21:20 -07:00
public void setProgressChangedFilter ( RNCWebView . ProgressChangedFilter filter ) {
progressChangedFilter = filter ;
}
2019-03-19 15:42:47 +01:00
}
2019-05-16 15:22:08 -07:00
protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
protected static final FrameLayout . LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout . LayoutParams (
LayoutParams . MATCH_PARENT , LayoutParams . MATCH_PARENT , Gravity . CENTER );
@RequiresApi ( api = Build . VERSION_CODES . KITKAT )
protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View . SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View . SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View . SYSTEM_UI_FLAG_LAYOUT_STABLE |
View . SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View . SYSTEM_UI_FLAG_FULLSCREEN |
View . SYSTEM_UI_FLAG_IMMERSIVE |
View . SYSTEM_UI_FLAG_IMMERSIVE_STICKY ;
protected ReactContext mReactContext ;
protected View mWebView ;
protected View mVideoView ;
protected WebChromeClient . CustomViewCallback mCustomViewCallback ;
2020-05-28 17:21:20 -07:00
protected RNCWebView . ProgressChangedFilter progressChangedFilter = null ;
2019-05-16 15:22:08 -07:00
public RNCWebChromeClient ( ReactContext reactContext , WebView webView ) {
this . mReactContext = reactContext ;
this . mWebView = webView ;
}
@Override
public boolean onConsoleMessage ( ConsoleMessage message ) {
if ( ReactBuildConfig . DEBUG ) {
return super . onConsoleMessage ( message );
}
// Ignore console logs in non debug builds.
return true ;
}
2019-08-02 12:23:59 -03:00
// Fix WebRTC permission request error.
2019-10-11 03:45:22 -05:00
@TargetApi ( Build . VERSION_CODES . LOLLIPOP )
2019-08-02 12:23:59 -03:00
@Override
public void onPermissionRequest ( final PermissionRequest request ) {
String [] requestedResources = request . getResources ();
ArrayList < String > permissions = new ArrayList <> ();
ArrayList < String > grantedPermissions = new ArrayList < String > ();
for ( int i = 0 ; i < requestedResources . length ; i ++ ) {
if ( requestedResources [ i ] . equals ( PermissionRequest . RESOURCE_AUDIO_CAPTURE )) {
permissions . add ( Manifest . permission . RECORD_AUDIO );
} else if ( requestedResources [ i ] . equals ( PermissionRequest . RESOURCE_VIDEO_CAPTURE )) {
permissions . add ( Manifest . permission . CAMERA );
}
// TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
}
for ( int i = 0 ; i < permissions . size (); i ++ ) {
if ( ContextCompat . checkSelfPermission ( mReactContext , permissions . get ( i )) != PackageManager . PERMISSION_GRANTED ) {
continue ;
}
if ( permissions . get ( i ). equals ( Manifest . permission . RECORD_AUDIO )) {
grantedPermissions . add ( PermissionRequest . RESOURCE_AUDIO_CAPTURE );
} else if ( permissions . get ( i ). equals ( Manifest . permission . CAMERA )) {
grantedPermissions . add ( PermissionRequest . RESOURCE_VIDEO_CAPTURE );
}
}
if ( grantedPermissions . isEmpty ()) {
request . deny ();
} else {
String [] grantedPermissionsArray = new String [ grantedPermissions . size () ] ;
grantedPermissionsArray = grantedPermissions . toArray ( grantedPermissionsArray );
request . grant ( grantedPermissionsArray );
}
}
2019-05-16 15:22:08 -07:00
@Override
public void onProgressChanged ( WebView webView , int newProgress ) {
super . onProgressChanged ( webView , newProgress );
2019-09-23 01:35:49 -07:00
final String url = webView . getUrl ();
2020-05-28 17:21:20 -07:00
if ( progressChangedFilter . isWaitingForCommandLoadUrl ()) {
2019-09-23 01:35:49 -07:00
return ;
}
2019-05-16 15:22:08 -07:00
WritableMap event = Arguments . createMap ();
event . putDouble ( "target" , webView . getId ());
event . putString ( "title" , webView . getTitle ());
2019-09-23 01:35:49 -07:00
event . putString ( "url" , url );
2019-05-16 15:22:08 -07:00
event . putBoolean ( "canGoBack" , webView . canGoBack ());
event . putBoolean ( "canGoForward" , webView . canGoForward ());
event . putDouble ( "progress" , ( float ) newProgress / 100 );
dispatchEvent (
webView ,
new TopLoadingProgressEvent (
webView . getId (),
event ));
}
@Override
public void onGeolocationPermissionsShowPrompt ( String origin , GeolocationPermissions . Callback callback ) {
callback . invoke ( origin , true , false );
}
protected void openFileChooser ( ValueCallback < Uri > filePathCallback , String acceptType ) {
getModule ( mReactContext ). startPhotoPickerIntent ( filePathCallback , acceptType );
}
protected void openFileChooser ( ValueCallback < Uri > filePathCallback ) {
getModule ( mReactContext ). startPhotoPickerIntent ( filePathCallback , "" );
}
protected void openFileChooser ( ValueCallback < Uri > filePathCallback , String acceptType , String capture ) {
getModule ( mReactContext ). startPhotoPickerIntent ( filePathCallback , acceptType );
}
@TargetApi ( Build . VERSION_CODES . LOLLIPOP )
@Override
public boolean onShowFileChooser ( WebView webView , ValueCallback < Uri []> filePathCallback , FileChooserParams fileChooserParams ) {
String [] acceptTypes = fileChooserParams . getAcceptTypes ();
boolean allowMultiple = fileChooserParams . getMode () == WebChromeClient . FileChooserParams . MODE_OPEN_MULTIPLE ;
2020-05-26 19:59:08 -07:00
return getModule ( mReactContext ). startPhotoPickerIntent ( filePathCallback , acceptTypes , allowMultiple );
2019-05-16 15:22:08 -07:00
}
@Override
public void onHostResume () {
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . KITKAT && mVideoView != null && mVideoView . getSystemUiVisibility () != FULLSCREEN_SYSTEM_UI_VISIBILITY ) {
mVideoView . setSystemUiVisibility ( FULLSCREEN_SYSTEM_UI_VISIBILITY );
}
}
@Override
public void onHostPause () { }
@Override
public void onHostDestroy () { }
protected ViewGroup getRootView () {
return ( ViewGroup ) mReactContext . getCurrentActivity (). findViewById ( android . R . id . content );
}
2020-05-28 17:21:20 -07:00
public void setProgressChangedFilter ( RNCWebView . ProgressChangedFilter filter ) {
progressChangedFilter = filter ;
}
2019-05-16 15:22:08 -07:00
}
2019-03-19 15:42:47 +01:00
/**
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
* to call {@link WebView#destroy} on activity destroy event and also to clear the client
*/
protected static class RNCWebView extends WebView implements LifecycleEventListener {
protected @Nullable
String injectedJS ;
2020-06-13 16:54:48 -04:00
protected @Nullable
String injectedJSBeforeContentLoaded ;
/**
* android.webkit.WebChromeClient fundamentally does not support JS injection into frames other
* than the main frame, so these two properties are mostly here just for parity with iOS & macOS.
*/
protected boolean injectedJavaScriptForMainFrameOnly = true ;
protected boolean injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true ;
2019-03-19 15:42:47 +01:00
protected boolean messagingEnabled = false ;
protected @Nullable
2020-04-09 18:05:44 +03:00
String messagingModuleName ;
protected @Nullable
2019-03-19 15:42:47 +01:00
RNCWebViewClient mRNCWebViewClient ;
2020-04-06 20:25:28 +03:00
protected @Nullable
CatalystInstance mCatalystInstance ;
2019-03-19 15:42:47 +01:00
protected boolean sendContentSizeChangeEvents = false ;
2019-06-03 03:19:39 +09:00
private OnScrollDispatchHelper mOnScrollDispatchHelper ;
protected boolean hasScrollEvent = false ;
2020-05-28 17:21:20 -07:00
protected ProgressChangedFilter progressChangedFilter ;
2019-03-19 15:42:47 +01:00
/**
* WebView must be created with an context of the current activity
* <p>
* Activity Context is required for creation of dialogs internally by WebView
* Reactive Native needed for access to ReactNative internal system functionality
*/
public RNCWebView ( ThemedReactContext reactContext ) {
super ( reactContext );
2020-08-25 23:59:51 -07:00
this . createCatalystInstance ();
2020-05-28 17:21:20 -07:00
progressChangedFilter = new ProgressChangedFilter ();
2019-03-19 15:42:47 +01:00
}
2020-04-10 13:41:53 -07:00
public void setIgnoreErrFailedForThisURL ( String url ) {
mRNCWebViewClient . setIgnoreErrFailedForThisURL ( url );
}
2019-03-19 15:42:47 +01:00
public void setSendContentSizeChangeEvents ( boolean sendContentSizeChangeEvents ) {
this . sendContentSizeChangeEvents = sendContentSizeChangeEvents ;
}
2019-06-03 03:19:39 +09:00
public void setHasScrollEvent ( boolean hasScrollEvent ) {
this . hasScrollEvent = hasScrollEvent ;
}
2019-03-19 15:42:47 +01:00
@Override
public void onHostResume () {
// do nothing
}
@Override
public void onHostPause () {
// do nothing
}
@Override
public void onHostDestroy () {
cleanupCallbacksAndDestroy ();
}
@Override
protected void onSizeChanged ( int w , int h , int ow , int oh ) {
super . onSizeChanged ( w , h , ow , oh );
if ( sendContentSizeChangeEvents ) {
dispatchEvent (
this ,
new ContentSizeChangeEvent (
this . getId (),
w ,
h
)
);
}
}
@Override
public void setWebViewClient ( WebViewClient client ) {
super . setWebViewClient ( client );
2019-11-13 02:36:08 -07:00
if ( client instanceof RNCWebViewClient ) {
mRNCWebViewClient = ( RNCWebViewClient ) client ;
2020-05-28 17:21:20 -07:00
mRNCWebViewClient . setProgressChangedFilter ( progressChangedFilter );
}
}
2020-05-29 18:21:24 +02:00
WebChromeClient mWebChromeClient ;
2020-05-28 17:21:20 -07:00
@Override
public void setWebChromeClient ( WebChromeClient client ) {
2020-05-29 18:21:24 +02:00
this . mWebChromeClient = client ;
2020-05-28 17:21:20 -07:00
super . setWebChromeClient ( client );
if ( client instanceof RNCWebChromeClient ) {
(( RNCWebChromeClient ) client ). setProgressChangedFilter ( progressChangedFilter );
2019-11-13 02:36:08 -07:00
}
2019-03-19 15:42:47 +01:00
}
public @Nullable
RNCWebViewClient getRNCWebViewClient () {
return mRNCWebViewClient ;
}
public void setInjectedJavaScript ( @Nullable String js ) {
injectedJS = js ;
}
2020-06-13 16:54:48 -04:00
public void setInjectedJavaScriptBeforeContentLoaded ( @Nullable String js ) {
injectedJSBeforeContentLoaded = js ;
}
public void setInjectedJavaScriptForMainFrameOnly ( boolean enabled ) {
injectedJavaScriptForMainFrameOnly = enabled ;
}
public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly ( boolean enabled ) {
injectedJavaScriptBeforeContentLoadedForMainFrameOnly = enabled ;
}
2019-03-19 15:42:47 +01:00
protected RNCWebViewBridge createRNCWebViewBridge ( RNCWebView webView ) {
return new RNCWebViewBridge ( webView );
}
2020-04-06 20:25:28 +03:00
protected void createCatalystInstance () {
ReactContext reactContext = ( ReactContext ) this . getContext ();
if ( reactContext != null ) {
mCatalystInstance = reactContext . getCatalystInstance ();
}
}
2019-03-19 15:42:47 +01:00
@SuppressLint ( "AddJavascriptInterface" )
public void setMessagingEnabled ( boolean enabled ) {
if ( messagingEnabled == enabled ) {
return ;
}
messagingEnabled = enabled ;
if ( enabled ) {
addJavascriptInterface ( createRNCWebViewBridge ( this ), JAVASCRIPT_INTERFACE );
} else {
removeJavascriptInterface ( JAVASCRIPT_INTERFACE );
}
}
2020-04-09 18:05:44 +03:00
public void setMessagingModuleName ( String moduleName ) {
messagingModuleName = moduleName ;
}
2019-03-19 15:42:47 +01:00
protected void evaluateJavascriptWithFallback ( String script ) {
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . KITKAT ) {
evaluateJavascript ( script , null );
return ;
}
try {
loadUrl ( "javascript:" + URLEncoder . encode ( script , "UTF-8" ));
} catch ( UnsupportedEncodingException e ) {
// UTF-8 should always be supported
throw new RuntimeException ( e );
}
}
public void callInjectedJavaScript () {
if ( getSettings (). getJavaScriptEnabled () &&
injectedJS != null &&
! TextUtils . isEmpty ( injectedJS )) {
evaluateJavascriptWithFallback ( "(function() {\n" + injectedJS + ";\n})();" );
}
}
2020-06-13 16:54:48 -04:00
public void callInjectedJavaScriptBeforeContentLoaded () {
if ( getSettings (). getJavaScriptEnabled () &&
injectedJSBeforeContentLoaded != null &&
! TextUtils . isEmpty ( injectedJSBeforeContentLoaded )) {
evaluateJavascriptWithFallback ( "(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();" );
}
}
2019-03-19 15:42:47 +01:00
public void onMessage ( String message ) {
2020-04-06 20:25:28 +03:00
ReactContext reactContext = ( ReactContext ) this . getContext ();
RNCWebView mContext = this ;
2019-08-02 13:36:11 +05:30
if ( mRNCWebViewClient != null ) {
WebView webView = this ;
webView . post ( new Runnable () {
@Override
public void run () {
2019-08-09 16:53:00 +08:00
if ( mRNCWebViewClient == null ) {
return ;
}
2019-08-02 13:36:11 +05:30
WritableMap data = mRNCWebViewClient . createWebViewEvent ( webView , webView . getUrl ());
data . putString ( "data" , message );
2020-04-06 20:25:28 +03:00
if ( mCatalystInstance != null ) {
2020-08-25 23:59:51 -07:00
mContext . sendDirectMessage ( "onMessage" , data );
2020-04-06 20:25:28 +03:00
} else {
dispatchEvent ( webView , new TopMessageEvent ( webView . getId (), data ));
}
2019-08-02 13:36:11 +05:30
}
});
} else {
WritableMap eventData = Arguments . createMap ();
eventData . putString ( "data" , message );
2020-04-06 20:25:28 +03:00
if ( mCatalystInstance != null ) {
2020-08-25 23:59:51 -07:00
this . sendDirectMessage ( "onMessage" , eventData );
2020-04-06 20:25:28 +03:00
} else {
dispatchEvent ( this , new TopMessageEvent ( this . getId (), eventData ));
}
2019-08-02 13:36:11 +05:30
}
2019-03-19 15:42:47 +01:00
}
2020-08-25 23:59:51 -07:00
protected void sendDirectMessage ( final String method , WritableMap data ) {
2020-04-06 20:25:28 +03:00
WritableNativeMap event = new WritableNativeMap ();
event . putMap ( "nativeEvent" , data );
WritableNativeArray params = new WritableNativeArray ();
params . pushMap ( event );
2020-08-25 23:59:51 -07:00
mCatalystInstance . callFunction ( messagingModuleName , method , params );
2020-04-06 20:25:28 +03:00
}
2019-05-16 18:27:16 -04:00
protected void onScrollChanged ( int x , int y , int oldX , int oldY ) {
super . onScrollChanged ( x , y , oldX , oldY );
2019-06-03 03:19:39 +09:00
if ( ! hasScrollEvent ) {
return ;
}
if ( mOnScrollDispatchHelper == null ) {
mOnScrollDispatchHelper = new OnScrollDispatchHelper ();
}
2019-05-16 18:27:16 -04:00
if ( mOnScrollDispatchHelper . onScrollChanged ( x , y )) {
ScrollEvent event = ScrollEvent . obtain (
this . getId (),
ScrollEventType . SCROLL ,
x ,
y ,
mOnScrollDispatchHelper . getXFlingVelocity (),
mOnScrollDispatchHelper . getYFlingVelocity (),
this . computeHorizontalScrollRange (),
this . computeVerticalScrollRange (),
this . getWidth (),
this . getHeight ());
dispatchEvent ( this , event );
}
}
2019-03-19 15:42:47 +01:00
protected void cleanupCallbacksAndDestroy () {
setWebViewClient ( null );
destroy ();
}
2020-05-29 03:05:53 +02:00
@Override
public void destroy () {
2020-05-29 18:21:24 +02:00
if ( mWebChromeClient != null ) {
2020-05-29 03:05:53 +02:00
mWebChromeClient . onHideCustomView ();
}
super . destroy ();
}
2019-03-19 15:42:47 +01:00
protected class RNCWebViewBridge {
RNCWebView mContext ;
RNCWebViewBridge ( RNCWebView c ) {
mContext = c ;
}
/**
* This method is called whenever JavaScript running within the web view calls:
* - window[JAVASCRIPT_INTERFACE].postMessage
*/
@JavascriptInterface
public void postMessage ( String message ) {
mContext . onMessage ( message );
}
}
2020-05-28 17:21:20 -07:00
protected static class ProgressChangedFilter {
private boolean waitingForCommandLoadUrl = false ;
public void setWaitingForCommandLoadUrl ( boolean isWaiting ) {
waitingForCommandLoadUrl = isWaiting ;
}
public boolean isWaitingForCommandLoadUrl () {
return waitingForCommandLoadUrl ;
}
}
2019-03-19 15:42:47 +01:00
}
2019-08-29 09:38:25 -05:00
}