feat(android): WebView crash handling (#1480)

Co-authored-by: Cristiano Coelho <cristianocca@hotmail.com>
This commit is contained in:
cristianoccazinsp 2020-08-06 17:21:01 -03:00 committed by GitHub
parent 8081443c53
commit 8a8b7ceb98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 5 deletions

View File

@ -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<WebView> {
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;
}
@ -899,6 +903,41 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
}
}
@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,

View File

@ -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<TopRenderProcessGoneEvent>(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)
}

View File

@ -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)<!-- Link generated with jump2header -->
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
<WebView
source={{ uri: 'https://reactnative.dev' }}
onRenderProcessGone={syntheticEvent => {
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)<!-- Link generated with jump2header -->
Function that is invoked when the webview calls `window.ReactNativeWebView.postMessage`. Setting this property will inject this global into your webview.

View File

@ -21,6 +21,7 @@ import {
defaultRenderLoading,
} from './WebViewShared';
import {
WebViewRenderProcessGoneEvent,
WebViewErrorEvent,
WebViewHttpErrorEvent,
WebViewMessageEvent,
@ -228,6 +229,13 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
}
}
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<AndroidWebViewProps, State> {
onLoadingProgress={this.onLoadingProgress}
onLoadingStart={this.onLoadingStart}
onHttpError={this.onHttpError}
onRenderProcessGone={this.onRenderProcessGone}
onMessage={this.onMessage}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
ref={this.webViewRef}

View File

@ -137,6 +137,10 @@ export interface WebViewHttpError extends WebViewNativeEvent {
statusCode: number;
}
export interface WebViewRenderProcessGoneDetail {
didCrash: boolean;
}
export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
export type WebViewProgressEvent = NativeSyntheticEvent<
@ -155,6 +159,8 @@ export type WebViewTerminatedEvent = NativeSyntheticEvent<WebViewNativeEvent>;
export type WebViewHttpErrorEvent = NativeSyntheticEvent<WebViewHttpError>;
export type WebViewRenderProcessGoneEvent = NativeSyntheticEvent<WebViewRenderProcessGoneDetail>;
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: