Skip to content

Commit 8b96334

Browse files
feat(android): Add sub resource SSL error handling on Android (react-native-webview#3834)
* feat: add a handler for sub-resource SSL errors on Android * chore: add example for sub-resource SSL error handling * fix: make new prop optional
1 parent 56989ca commit 8b96334

File tree

10 files changed

+117
-6
lines changed

10 files changed

+117
-6
lines changed

android/src/main/java/com/reactnativecommunity/webview/RNCWebViewClient.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.facebook.react.bridge.WritableMap;
2626
import com.facebook.react.uimanager.ThemedReactContext;
2727
import com.facebook.react.uimanager.UIManagerHelper;
28+
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
2829
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
2930
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
3031
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
@@ -168,12 +169,6 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand
168169
// Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
169170
handler.cancel();
170171

171-
if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
172-
// If error is not due to top-level navigation, then do not call onReceivedError()
173-
Log.w(TAG, "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
174-
return;
175-
}
176-
177172
int code = error.getPrimaryError();
178173
String description = "";
179174
String descriptionPrefix = "SSL error: ";
@@ -205,6 +200,18 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand
205200

206201
description = descriptionPrefix + description;
207202

203+
if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
204+
// If error is not due to top-level navigation, then do not call onReceivedError()
205+
Log.w(TAG, "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
206+
this.onReceivedSubResourceSslError(
207+
webView,
208+
code,
209+
description,
210+
failingUrl
211+
);
212+
return;
213+
}
214+
208215
this.onReceivedError(
209216
webView,
210217
code,
@@ -213,6 +220,20 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand
213220
);
214221
}
215222

223+
public void onReceivedSubResourceSslError(
224+
WebView webView,
225+
int errorCode,
226+
String description,
227+
String failingUrl) {
228+
229+
WritableMap eventData = createWebViewEvent(webView, failingUrl);
230+
eventData.putDouble("code", errorCode);
231+
eventData.putString("description", description);
232+
233+
int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
234+
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) webView.getContext(), reactTag).dispatchEvent(new SubResourceErrorEvent(reactTag, eventData));
235+
}
236+
216237
@Override
217238
public void onReceivedError(
218239
WebView webView,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 there is an error in loading a subresource
9+
*/
10+
class SubResourceErrorEvent(viewId: Int, private val mEventData: WritableMap) :
11+
Event<SubResourceErrorEvent>(viewId) {
12+
companion object {
13+
const val EVENT_NAME = "topLoadingSubResourceError"
14+
}
15+
16+
override fun getEventName(): String = EVENT_NAME
17+
18+
override fun canCoalesce(): Boolean = false
19+
20+
override fun getCoalescingKey(): Short = 0
21+
22+
override fun dispatch(rctEventEmitter: RCTEventEmitter) =
23+
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
24+
25+
}

android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.facebook.react.viewmanagers.RNCWebViewManagerInterface;
1616
import com.facebook.react.views.scroll.ScrollEventType;
1717
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
18+
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
1819
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
1920
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
2021
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
@@ -524,6 +525,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
524525
export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
525526
export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
526527
export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
528+
export.put(SubResourceErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingSubResourceError"));
527529
export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
528530
// !Default events but adding them here explicitly for clarity
529531

android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.facebook.react.uimanager.annotations.ReactProp;
1212
import com.facebook.react.views.scroll.ScrollEventType;
1313
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
14+
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
1415
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
1516
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
1617
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
@@ -294,6 +295,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
294295
export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
295296
export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
296297
export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
298+
export.put(SubResourceErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingSubResourceError"));
297299
export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
298300
// !Default events but adding them here explicitly for clarity
299301

example/App.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import CustomMenu from './examples/CustomMenu';
2626
import OpenWindow from './examples/OpenWindow';
2727
import SuppressMenuItems from './examples/Suppress';
2828
import ClearData from './examples/ClearData';
29+
import SslError from './examples/SslError';
2930

3031
const TESTS = {
3132
Messaging: {
@@ -156,6 +157,14 @@ const TESTS = {
156157
return <SuppressMenuItems />;
157158
},
158159
},
160+
SslError: {
161+
title: 'SslError',
162+
testId: 'SslError',
163+
description: 'SSL error test',
164+
render() {
165+
return <SslError />;
166+
},
167+
},
159168
};
160169

161170
interface Props {}
@@ -286,6 +295,11 @@ export default class App extends Component<Props, State> {
286295
title="ClearData"
287296
onPress={() => this._changeTest('ClearData')}
288297
/>
298+
<Button
299+
testID="testType_sslError"
300+
title="SslError"
301+
onPress={() => this._changeTest('SslError')}
302+
/>
289303
</View>
290304

291305
{restarting ? null : (

example/examples/SslError.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React, {Component} from 'react';
2+
import {View} from 'react-native';
3+
4+
import WebView from 'react-native-webview';
5+
6+
type Props = {};
7+
type State = {};
8+
9+
export default class SslError extends Component<Props, State> {
10+
state = {};
11+
12+
render() {
13+
return (
14+
<View style={{ flex: 1 }}>
15+
<WebView
16+
source={{uri: "https://badssl.com/"}}
17+
onLoadSubResourceError={(event) => {
18+
console.log('onLoadSubResourceError', event.nativeEvent.description);
19+
}}
20+
/>
21+
</View>
22+
);
23+
}
24+
}

src/RNCWebViewNativeComponent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ export interface NativeProps extends ViewProps {
270270
mediaPlaybackRequiresUserAction?: WithDefault<boolean, true>;
271271
messagingEnabled: boolean;
272272
onLoadingError: DirectEventHandler<WebViewErrorEvent>;
273+
onLoadingSubResourceError: DirectEventHandler<WebViewErrorEvent>;
273274
onLoadingFinish: DirectEventHandler<WebViewNavigationEvent>;
274275
onLoadingProgress: DirectEventHandler<WebViewNativeProgressEvent>;
275276
onLoadingStart: DirectEventHandler<WebViewNavigationEvent>;

src/WebView.android.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
8383
onError,
8484
onLoad,
8585
onLoadEnd,
86+
onLoadSubResourceError,
8687
onLoadProgress,
8788
onHttpError: onHttpErrorProp,
8889
onRenderProcessGone: onRenderProcessGoneProp,
@@ -130,6 +131,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
130131
lastErrorEvent,
131132
onHttpError,
132133
onLoadingError,
134+
onLoadingSubResourceError,
133135
onLoadingFinish,
134136
onLoadingProgress,
135137
onOpenWindow,
@@ -139,6 +141,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
139141
onLoad,
140142
onError,
141143
onHttpErrorProp,
144+
onLoadSubResourceError,
142145
onLoadEnd,
143146
onLoadProgress,
144147
onLoadStart,
@@ -284,6 +287,7 @@ const WebViewComponent = forwardRef<{}, AndroidWebViewProps>(
284287
messagingModuleName={messagingModuleName}
285288
hasOnScroll={!!otherProps.onScroll}
286289
onLoadingError={onLoadingError}
290+
onLoadingSubResourceError={onLoadingSubResourceError}
287291
onLoadingFinish={onLoadingFinish}
288292
onLoadingProgress={onLoadingProgress}
289293
onLoadingStart={onLoadingStart}

src/WebViewShared.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export const useWebViewLogic = ({
104104
onLoadProgress,
105105
onLoadEnd,
106106
onError,
107+
onLoadSubResourceError,
107108
onHttpErrorProp,
108109
onMessageProp,
109110
onOpenWindowProp,
@@ -120,6 +121,7 @@ export const useWebViewLogic = ({
120121
onLoadProgress?: (event: WebViewProgressEvent) => void;
121122
onLoadEnd?: (event: WebViewNavigationEvent | WebViewErrorEvent) => void;
122123
onError?: (event: WebViewErrorEvent) => void;
124+
onLoadSubResourceError?: (event: WebViewErrorEvent) => void;
123125
onHttpErrorProp?: (event: WebViewHttpErrorEvent) => void;
124126
onMessageProp?: (event: WebViewMessageEvent) => void;
125127
onOpenWindowProp?: (event: WebViewOpenWindowEvent) => void;
@@ -178,6 +180,13 @@ export const useWebViewLogic = ({
178180
[onError, onLoadEnd]
179181
);
180182

183+
const onLoadingSubResourceError = useCallback(
184+
(event: WebViewErrorEvent) => {
185+
onLoadSubResourceError?.(event);
186+
},
187+
[onLoadSubResourceError]
188+
);
189+
181190
const onHttpError = useCallback(
182191
(event: WebViewHttpErrorEvent) => {
183192
onHttpErrorProp?.(event);
@@ -270,6 +279,7 @@ export const useWebViewLogic = ({
270279
onLoadingStart,
271280
onLoadingProgress,
272281
onLoadingError,
282+
onLoadingSubResourceError,
273283
onLoadingFinish,
274284
onHttpError,
275285
onRenderProcessGone,

src/WebViewTypes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,14 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
11601160
* @platform android
11611161
*/
11621162
allowsProtectedMedia?: boolean;
1163+
1164+
/**
1165+
* Function that is invoked when the `WebView` receives an SSL error for a sub-resource.
1166+
*
1167+
* @param event
1168+
* @platform android
1169+
*/
1170+
onLoadSubResourceError?: (event: WebViewErrorEvent) => void;
11631171
}
11641172

11651173
export interface WebViewSharedProps extends ViewProps {

0 commit comments

Comments
 (0)