diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index ab869cf..52f769a 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -17,6 +17,7 @@ import android.os.Environment; import androidx.annotation.RequiresApi; import androidx.core.content.ContextCompat; import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -27,6 +28,7 @@ import android.webkit.CookieManager; import android.webkit.DownloadListener; import android.webkit.GeolocationPermissions; import android.webkit.JavascriptInterface; +import android.webkit.RenderProcessGoneDetail; import android.webkit.SslErrorHandler; import android.webkit.PermissionRequest; import android.webkit.URLUtil; @@ -69,6 +71,7 @@ import com.reactnativecommunity.webview.events.TopLoadingProgressEvent; import com.reactnativecommunity.webview.events.TopLoadingStartEvent; import com.reactnativecommunity.webview.events.TopMessageEvent; import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent; +import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent; import org.json.JSONException; import org.json.JSONObject; @@ -575,6 +578,7 @@ public class RNCWebViewManager extends SimpleViewManager { export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest")); export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll")); export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError")); + export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone")); return export; } @@ -771,7 +775,7 @@ public class RNCWebViewManager extends SimpleViewManager { mLastLoadFailed = false; RNCWebView reactWebView = (RNCWebView) webView; - reactWebView.callInjectedJavaScriptBeforeContentLoaded(); + reactWebView.callInjectedJavaScriptBeforeContentLoaded(); dispatchEvent( webView, @@ -828,11 +832,11 @@ public class RNCWebViewManager extends SimpleViewManager { case SslError.SSL_UNTRUSTED: description = "The certificate authority is not trusted"; break; - default: + default: description = "Unknown SSL Error"; break; } - + description = descriptionPrefix + description; this.onReceivedError( @@ -842,7 +846,7 @@ public class RNCWebViewManager extends SimpleViewManager { failingUrl ); } - + @Override public void onReceivedError( WebView webView, @@ -899,6 +903,41 @@ public class RNCWebViewManager extends SimpleViewManager { } } + @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; + } + protected void emitFinishEvent(WebView webView, String url) { dispatchEvent( webView, diff --git a/android/src/main/java/com/reactnativecommunity/webview/events/TopRenderProcessGoneEvent.kt b/android/src/main/java/com/reactnativecommunity/webview/events/TopRenderProcessGoneEvent.kt new file mode 100644 index 0000000..b87f4fa --- /dev/null +++ b/android/src/main/java/com/reactnativecommunity/webview/events/TopRenderProcessGoneEvent.kt @@ -0,0 +1,26 @@ +package com.reactnativecommunity.webview.events + +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +/** + * Event emitted when the WebView's process has crashed or + was killed by the OS. + */ +class TopRenderProcessGoneEvent(viewId: Int, private val mEventData: WritableMap) : + Event(viewId) { + companion object { + const val EVENT_NAME = "topRenderProcessGone" + } + + override fun getEventName(): String = EVENT_NAME + + override fun canCoalesce(): Boolean = false + + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) = + rctEventEmitter.receiveEvent(viewTag, eventName, mEventData) + +} diff --git a/docs/Reference.md b/docs/Reference.md index 53d959c..3b53bc5 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -13,6 +13,7 @@ This document lays out the current public properties and methods for the React N - [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction) - [`nativeConfig`](Reference.md#nativeconfig) - [`onError`](Reference.md#onerror) +- [`onRenderProcessGone`](Reference.md#onRenderProcessGone) - [`onLoad`](Reference.md#onload) - [`onLoadEnd`](Reference.md#onloadend) - [`onLoadStart`](Reference.md#onloadstart) @@ -458,6 +459,39 @@ url --- +### `onRenderProcessGone`[⬆](#props-index) + +Function that is invoked when the `WebView` process crashes or is killed by the OS on Android. + +> **_Note_** +> Android API minimum level 26. Android Only + +| Type | Required | +| -------- | -------- | +| function | No | + +Example: + +```jsx + { + const { nativeEvent } = syntheticEvent; + console.warn( + 'WebView Crashed: ', + nativeEvent.didCrash, + ); + }} +/> +``` + +Function passed to `onRenderProcessGone` is called with a SyntheticEvent wrapping a nativeEvent with these properties: + +``` +didCrash +``` +--- + ### `onMessage`[⬆](#props-index) Function that is invoked when the webview calls `window.ReactNativeWebView.postMessage`. Setting this property will inject this global into your webview. diff --git a/src/WebView.android.tsx b/src/WebView.android.tsx index 43600b4..5e68a35 100644 --- a/src/WebView.android.tsx +++ b/src/WebView.android.tsx @@ -21,6 +21,7 @@ import { defaultRenderLoading, } from './WebViewShared'; import { + WebViewRenderProcessGoneEvent, WebViewErrorEvent, WebViewHttpErrorEvent, WebViewMessageEvent, @@ -228,6 +229,13 @@ class WebView extends React.Component { } } + onRenderProcessGone = (event: WebViewRenderProcessGoneEvent) => { + const { onRenderProcessGone } = this.props; + if (onRenderProcessGone) { + onRenderProcessGone(event); + } + } + onLoadingFinish = (event: WebViewNavigationEvent) => { const { onLoad, onLoadEnd } = this.props; const { nativeEvent: { url } } = event; @@ -347,6 +355,7 @@ class WebView extends React.Component { onLoadingProgress={this.onLoadingProgress} onLoadingStart={this.onLoadingStart} onHttpError={this.onHttpError} + onRenderProcessGone={this.onRenderProcessGone} onMessage={this.onMessage} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} ref={this.webViewRef} diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 602b2e7..6252dd6 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -137,6 +137,10 @@ export interface WebViewHttpError extends WebViewNativeEvent { statusCode: number; } +export interface WebViewRenderProcessGoneDetail { + didCrash: boolean; +} + export type WebViewEvent = NativeSyntheticEvent; export type WebViewProgressEvent = NativeSyntheticEvent< @@ -155,6 +159,8 @@ export type WebViewTerminatedEvent = NativeSyntheticEvent; export type WebViewHttpErrorEvent = NativeSyntheticEvent; +export type WebViewRenderProcessGoneEvent = NativeSyntheticEvent; + export type DataDetectorTypes = | 'phoneNumber' | 'link' @@ -277,6 +283,7 @@ export interface AndroidNativeWebViewProps extends CommonNativeWebViewProps { javaScriptEnabled?: boolean; mixedContentMode?: 'never' | 'always' | 'compatibility'; onContentSizeChange?: (event: WebViewEvent) => void; + onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void; overScrollMode?: OverScrollModeType; saveFormDataDisabled?: boolean; textZoom?: number; @@ -683,6 +690,12 @@ export interface AndroidWebViewProps extends WebViewSharedProps { onNavigationStateChange?: (event: WebViewNavigation) => void; onContentSizeChange?: (event: WebViewEvent) => void; + /** + * Function that is invoked when the `WebView` process crashes or is killed by the OS. + * Works only on Android (minimum API level 26). + */ + onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void; + /** * https://developer.android.com/reference/android/webkit/WebSettings.html#setCacheMode(int) * Set the cacheMode. Possible values are: @@ -721,7 +734,7 @@ export interface AndroidWebViewProps extends WebViewSharedProps { */ geolocationEnabled?: boolean; - + /** * Boolean that sets whether JavaScript running in the context of a file * scheme URL should be allowed to access content from other file scheme URLs.