mirror of
https://github.com/status-im/react-native-webview.git
synced 2026-08-30 19:21:14 +00:00
feat(android): WebView crash handling (#1480)
Co-authored-by: Cristiano Coelho <cristianocca@hotmail.com>
This commit is contained in:
co-authored by
Cristiano Coelho
parent
8081443c53
commit
8a8b7ceb98
@@ -17,6 +17,7 @@ import android.os.Environment;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -27,6 +28,7 @@ import android.webkit.CookieManager;
|
|||||||
import android.webkit.DownloadListener;
|
import android.webkit.DownloadListener;
|
||||||
import android.webkit.GeolocationPermissions;
|
import android.webkit.GeolocationPermissions;
|
||||||
import android.webkit.JavascriptInterface;
|
import android.webkit.JavascriptInterface;
|
||||||
|
import android.webkit.RenderProcessGoneDetail;
|
||||||
import android.webkit.SslErrorHandler;
|
import android.webkit.SslErrorHandler;
|
||||||
import android.webkit.PermissionRequest;
|
import android.webkit.PermissionRequest;
|
||||||
import android.webkit.URLUtil;
|
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.TopLoadingStartEvent;
|
||||||
import com.reactnativecommunity.webview.events.TopMessageEvent;
|
import com.reactnativecommunity.webview.events.TopMessageEvent;
|
||||||
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
|
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
|
||||||
|
import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
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(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
|
||||||
export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
|
export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
|
||||||
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
|
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
|
||||||
|
export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
|
||||||
return export;
|
return export;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,7 +775,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||||||
mLastLoadFailed = false;
|
mLastLoadFailed = false;
|
||||||
|
|
||||||
RNCWebView reactWebView = (RNCWebView) webView;
|
RNCWebView reactWebView = (RNCWebView) webView;
|
||||||
reactWebView.callInjectedJavaScriptBeforeContentLoaded();
|
reactWebView.callInjectedJavaScriptBeforeContentLoaded();
|
||||||
|
|
||||||
dispatchEvent(
|
dispatchEvent(
|
||||||
webView,
|
webView,
|
||||||
@@ -828,11 +832,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||||||
case SslError.SSL_UNTRUSTED:
|
case SslError.SSL_UNTRUSTED:
|
||||||
description = "The certificate authority is not trusted";
|
description = "The certificate authority is not trusted";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
description = "Unknown SSL Error";
|
description = "Unknown SSL Error";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
description = descriptionPrefix + description;
|
description = descriptionPrefix + description;
|
||||||
|
|
||||||
this.onReceivedError(
|
this.onReceivedError(
|
||||||
@@ -842,7 +846,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||||||
failingUrl
|
failingUrl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceivedError(
|
public void onReceivedError(
|
||||||
WebView webView,
|
WebView webView,
|
||||||
@@ -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) {
|
protected void emitFinishEvent(WebView webView, String url) {
|
||||||
dispatchEvent(
|
dispatchEvent(
|
||||||
webView,
|
webView,
|
||||||
|
|||||||
+26
@@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ This document lays out the current public properties and methods for the React N
|
|||||||
- [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
|
- [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
|
||||||
- [`nativeConfig`](Reference.md#nativeconfig)
|
- [`nativeConfig`](Reference.md#nativeconfig)
|
||||||
- [`onError`](Reference.md#onerror)
|
- [`onError`](Reference.md#onerror)
|
||||||
|
- [`onRenderProcessGone`](Reference.md#onRenderProcessGone)
|
||||||
- [`onLoad`](Reference.md#onload)
|
- [`onLoad`](Reference.md#onload)
|
||||||
- [`onLoadEnd`](Reference.md#onloadend)
|
- [`onLoadEnd`](Reference.md#onloadend)
|
||||||
- [`onLoadStart`](Reference.md#onloadstart)
|
- [`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 -->
|
### `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.
|
Function that is invoked when the webview calls `window.ReactNativeWebView.postMessage`. Setting this property will inject this global into your webview.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
defaultRenderLoading,
|
defaultRenderLoading,
|
||||||
} from './WebViewShared';
|
} from './WebViewShared';
|
||||||
import {
|
import {
|
||||||
|
WebViewRenderProcessGoneEvent,
|
||||||
WebViewErrorEvent,
|
WebViewErrorEvent,
|
||||||
WebViewHttpErrorEvent,
|
WebViewHttpErrorEvent,
|
||||||
WebViewMessageEvent,
|
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) => {
|
onLoadingFinish = (event: WebViewNavigationEvent) => {
|
||||||
const { onLoad, onLoadEnd } = this.props;
|
const { onLoad, onLoadEnd } = this.props;
|
||||||
const { nativeEvent: { url } } = event;
|
const { nativeEvent: { url } } = event;
|
||||||
@@ -347,6 +355,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
|
|||||||
onLoadingProgress={this.onLoadingProgress}
|
onLoadingProgress={this.onLoadingProgress}
|
||||||
onLoadingStart={this.onLoadingStart}
|
onLoadingStart={this.onLoadingStart}
|
||||||
onHttpError={this.onHttpError}
|
onHttpError={this.onHttpError}
|
||||||
|
onRenderProcessGone={this.onRenderProcessGone}
|
||||||
onMessage={this.onMessage}
|
onMessage={this.onMessage}
|
||||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||||
ref={this.webViewRef}
|
ref={this.webViewRef}
|
||||||
|
|||||||
+14
-1
@@ -137,6 +137,10 @@ export interface WebViewHttpError extends WebViewNativeEvent {
|
|||||||
statusCode: number;
|
statusCode: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WebViewRenderProcessGoneDetail {
|
||||||
|
didCrash: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
|
export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
|
||||||
|
|
||||||
export type WebViewProgressEvent = NativeSyntheticEvent<
|
export type WebViewProgressEvent = NativeSyntheticEvent<
|
||||||
@@ -155,6 +159,8 @@ export type WebViewTerminatedEvent = NativeSyntheticEvent<WebViewNativeEvent>;
|
|||||||
|
|
||||||
export type WebViewHttpErrorEvent = NativeSyntheticEvent<WebViewHttpError>;
|
export type WebViewHttpErrorEvent = NativeSyntheticEvent<WebViewHttpError>;
|
||||||
|
|
||||||
|
export type WebViewRenderProcessGoneEvent = NativeSyntheticEvent<WebViewRenderProcessGoneDetail>;
|
||||||
|
|
||||||
export type DataDetectorTypes =
|
export type DataDetectorTypes =
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'link'
|
| 'link'
|
||||||
@@ -277,6 +283,7 @@ export interface AndroidNativeWebViewProps extends CommonNativeWebViewProps {
|
|||||||
javaScriptEnabled?: boolean;
|
javaScriptEnabled?: boolean;
|
||||||
mixedContentMode?: 'never' | 'always' | 'compatibility';
|
mixedContentMode?: 'never' | 'always' | 'compatibility';
|
||||||
onContentSizeChange?: (event: WebViewEvent) => void;
|
onContentSizeChange?: (event: WebViewEvent) => void;
|
||||||
|
onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void;
|
||||||
overScrollMode?: OverScrollModeType;
|
overScrollMode?: OverScrollModeType;
|
||||||
saveFormDataDisabled?: boolean;
|
saveFormDataDisabled?: boolean;
|
||||||
textZoom?: number;
|
textZoom?: number;
|
||||||
@@ -683,6 +690,12 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
|
|||||||
onNavigationStateChange?: (event: WebViewNavigation) => void;
|
onNavigationStateChange?: (event: WebViewNavigation) => void;
|
||||||
onContentSizeChange?: (event: WebViewEvent) => 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)
|
* https://developer.android.com/reference/android/webkit/WebSettings.html#setCacheMode(int)
|
||||||
* Set the cacheMode. Possible values are:
|
* Set the cacheMode. Possible values are:
|
||||||
@@ -721,7 +734,7 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
|
|||||||
*/
|
*/
|
||||||
geolocationEnabled?: boolean;
|
geolocationEnabled?: boolean;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean that sets whether JavaScript running in the context of a file
|
* 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.
|
* scheme URL should be allowed to access content from other file scheme URLs.
|
||||||
|
|||||||
Reference in New Issue
Block a user