Browse Source

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

Co-authored-by: Cristiano Coelho <cristianocca@hotmail.com>
cristianoccazinsp 4 years ago
parent
commit
8a8b7ceb98
No account linked to committer's email address

+ 43
- 4
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

@@ -17,6 +17,7 @@ import android.os.Environment;
17 17
 import androidx.annotation.RequiresApi;
18 18
 import androidx.core.content.ContextCompat;
19 19
 import android.text.TextUtils;
20
+import android.util.Log;
20 21
 import android.view.Gravity;
21 22
 import android.view.View;
22 23
 import android.view.ViewGroup;
@@ -27,6 +28,7 @@ import android.webkit.CookieManager;
27 28
 import android.webkit.DownloadListener;
28 29
 import android.webkit.GeolocationPermissions;
29 30
 import android.webkit.JavascriptInterface;
31
+import android.webkit.RenderProcessGoneDetail;
30 32
 import android.webkit.SslErrorHandler;
31 33
 import android.webkit.PermissionRequest;
32 34
 import android.webkit.URLUtil;
@@ -69,6 +71,7 @@ import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
69 71
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
70 72
 import com.reactnativecommunity.webview.events.TopMessageEvent;
71 73
 import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
74
+import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
72 75
 
73 76
 import org.json.JSONException;
74 77
 import org.json.JSONObject;
@@ -575,6 +578,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
575 578
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
576 579
     export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
577 580
     export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
581
+    export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
578 582
     return export;
579 583
   }
580 584
 
@@ -771,7 +775,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
771 775
       mLastLoadFailed = false;
772 776
 
773 777
       RNCWebView reactWebView = (RNCWebView) webView;
774
-      reactWebView.callInjectedJavaScriptBeforeContentLoaded();       
778
+      reactWebView.callInjectedJavaScriptBeforeContentLoaded();
775 779
 
776 780
       dispatchEvent(
777 781
         webView,
@@ -828,11 +832,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
828 832
           case SslError.SSL_UNTRUSTED:
829 833
             description = "The certificate authority is not trusted";
830 834
             break;
831
-          default: 
835
+          default:
832 836
             description = "Unknown SSL Error";
833 837
             break;
834 838
         }
835
-        
839
+
836 840
         description = descriptionPrefix + description;
837 841
 
838 842
         this.onReceivedError(
@@ -842,7 +846,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
842 846
           failingUrl
843 847
         );
844 848
     }
845
-    
849
+
846 850
     @Override
847 851
     public void onReceivedError(
848 852
       WebView webView,
@@ -899,6 +903,41 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
899 903
       }
900 904
     }
901 905
 
906
+    @TargetApi(Build.VERSION_CODES.O)
907
+    @Override
908
+    public boolean onRenderProcessGone(WebView webView, RenderProcessGoneDetail detail) {
909
+        // WebViewClient.onRenderProcessGone was added in O.
910
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
911
+            return false;
912
+        }
913
+        super.onRenderProcessGone(webView, detail);
914
+
915
+        if(detail.didCrash()){
916
+          Log.e("RNCWebViewManager", "The WebView rendering process crashed.");
917
+        }
918
+        else{
919
+          Log.w("RNCWebViewManager", "The WebView rendering process was killed by the system.");
920
+        }
921
+
922
+        // if webView is null, we cannot return any event
923
+        // since the view is already dead/disposed
924
+        // still prevent the app crash by returning true.
925
+        if(webView == null){
926
+          return true;
927
+        }
928
+
929
+        WritableMap event = createWebViewEvent(webView, webView.getUrl());
930
+        event.putBoolean("didCrash", detail.didCrash());
931
+
932
+        dispatchEvent(
933
+          webView,
934
+          new TopRenderProcessGoneEvent(webView.getId(), event)
935
+        );
936
+
937
+        // returning false would crash the app.
938
+        return true;
939
+    }
940
+
902 941
     protected void emitFinishEvent(WebView webView, String url) {
903 942
       dispatchEvent(
904 943
         webView,

+ 26
- 0
android/src/main/java/com/reactnativecommunity/webview/events/TopRenderProcessGoneEvent.kt View File

@@ -0,0 +1,26 @@
1
+package com.reactnativecommunity.webview.events
2
+
3
+import com.facebook.react.bridge.WritableMap
4
+import com.facebook.react.uimanager.events.Event
5
+import com.facebook.react.uimanager.events.RCTEventEmitter
6
+
7
+/**
8
+ * Event emitted when the WebView's process has crashed or
9
+   was killed by the OS.
10
+ */
11
+class TopRenderProcessGoneEvent(viewId: Int, private val mEventData: WritableMap) :
12
+  Event<TopRenderProcessGoneEvent>(viewId) {
13
+  companion object {
14
+    const val EVENT_NAME = "topRenderProcessGone"
15
+  }
16
+
17
+  override fun getEventName(): String = EVENT_NAME
18
+
19
+  override fun canCoalesce(): Boolean = false
20
+
21
+  override fun getCoalescingKey(): Short = 0
22
+
23
+  override fun dispatch(rctEventEmitter: RCTEventEmitter) =
24
+    rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
25
+
26
+}

+ 34
- 0
docs/Reference.md View File

@@ -13,6 +13,7 @@ This document lays out the current public properties and methods for the React N
13 13
 - [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
14 14
 - [`nativeConfig`](Reference.md#nativeconfig)
15 15
 - [`onError`](Reference.md#onerror)
16
+- [`onRenderProcessGone`](Reference.md#onRenderProcessGone)
16 17
 - [`onLoad`](Reference.md#onload)
17 18
 - [`onLoadEnd`](Reference.md#onloadend)
18 19
 - [`onLoadStart`](Reference.md#onloadstart)
@@ -458,6 +459,39 @@ url
458 459
 
459 460
 ---
460 461
 
462
+### `onRenderProcessGone`[⬆](#props-index)<!-- Link generated with jump2header -->
463
+
464
+Function that is invoked when the `WebView` process crashes or is killed by the OS on Android.
465
+
466
+> **_Note_**
467
+> Android API minimum level 26. Android Only
468
+
469
+| Type     | Required |
470
+| -------- | -------- |
471
+| function | No       |
472
+
473
+Example:
474
+
475
+```jsx
476
+<WebView
477
+  source={{ uri: 'https://reactnative.dev' }}
478
+  onRenderProcessGone={syntheticEvent => {
479
+    const { nativeEvent } = syntheticEvent;
480
+    console.warn(
481
+      'WebView Crashed: ',
482
+      nativeEvent.didCrash,
483
+    );
484
+  }}
485
+/>
486
+```
487
+
488
+Function passed to `onRenderProcessGone` is called with a SyntheticEvent wrapping a nativeEvent with these properties:
489
+
490
+```
491
+didCrash
492
+```
493
+---
494
+
461 495
 ### `onMessage`[⬆](#props-index)<!-- Link generated with jump2header -->
462 496
 
463 497
 Function that is invoked when the webview calls `window.ReactNativeWebView.postMessage`. Setting this property will inject this global into your webview.

+ 9
- 0
src/WebView.android.tsx View File

@@ -21,6 +21,7 @@ import {
21 21
   defaultRenderLoading,
22 22
 } from './WebViewShared';
23 23
 import {
24
+  WebViewRenderProcessGoneEvent,
24 25
   WebViewErrorEvent,
25 26
   WebViewHttpErrorEvent,
26 27
   WebViewMessageEvent,
@@ -228,6 +229,13 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
228 229
     }
229 230
   }
230 231
 
232
+  onRenderProcessGone = (event: WebViewRenderProcessGoneEvent) => {
233
+    const { onRenderProcessGone } = this.props;
234
+    if (onRenderProcessGone) {
235
+      onRenderProcessGone(event);
236
+    }
237
+  }
238
+
231 239
   onLoadingFinish = (event: WebViewNavigationEvent) => {
232 240
     const { onLoad, onLoadEnd } = this.props;
233 241
     const { nativeEvent: { url } } = event;
@@ -347,6 +355,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
347 355
         onLoadingProgress={this.onLoadingProgress}
348 356
         onLoadingStart={this.onLoadingStart}
349 357
         onHttpError={this.onHttpError}
358
+        onRenderProcessGone={this.onRenderProcessGone}
350 359
         onMessage={this.onMessage}
351 360
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
352 361
         ref={this.webViewRef}

+ 14
- 1
src/WebViewTypes.ts View File

@@ -137,6 +137,10 @@ export interface WebViewHttpError extends WebViewNativeEvent {
137 137
   statusCode: number;
138 138
 }
139 139
 
140
+export interface WebViewRenderProcessGoneDetail {
141
+  didCrash: boolean;
142
+}
143
+
140 144
 export type WebViewEvent = NativeSyntheticEvent<WebViewNativeEvent>;
141 145
 
142 146
 export type WebViewProgressEvent = NativeSyntheticEvent<
@@ -155,6 +159,8 @@ export type WebViewTerminatedEvent = NativeSyntheticEvent<WebViewNativeEvent>;
155 159
 
156 160
 export type WebViewHttpErrorEvent = NativeSyntheticEvent<WebViewHttpError>;
157 161
 
162
+export type WebViewRenderProcessGoneEvent = NativeSyntheticEvent<WebViewRenderProcessGoneDetail>;
163
+
158 164
 export type DataDetectorTypes =
159 165
   | 'phoneNumber'
160 166
   | 'link'
@@ -277,6 +283,7 @@ export interface AndroidNativeWebViewProps extends CommonNativeWebViewProps {
277 283
   javaScriptEnabled?: boolean;
278 284
   mixedContentMode?: 'never' | 'always' | 'compatibility';
279 285
   onContentSizeChange?: (event: WebViewEvent) => void;
286
+  onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void;
280 287
   overScrollMode?: OverScrollModeType;
281 288
   saveFormDataDisabled?: boolean;
282 289
   textZoom?: number;
@@ -683,6 +690,12 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
683 690
   onNavigationStateChange?: (event: WebViewNavigation) => void;
684 691
   onContentSizeChange?: (event: WebViewEvent) => void;
685 692
 
693
+  /**
694
+   * Function that is invoked when the `WebView` process crashes or is killed by the OS.
695
+   * Works only on Android (minimum API level 26).
696
+   */
697
+  onRenderProcessGone?: (event: WebViewRenderProcessGoneEvent) => void;
698
+
686 699
   /**
687 700
    * https://developer.android.com/reference/android/webkit/WebSettings.html#setCacheMode(int)
688 701
    * Set the cacheMode. Possible values are:
@@ -721,7 +734,7 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
721 734
    */
722 735
   geolocationEnabled?: boolean;
723 736
 
724
-  
737
+
725 738
   /**
726 739
    * Boolean that sets whether JavaScript running in the context of a file
727 740
    * scheme URL should be allowed to access content from other file scheme URLs.