diff --git a/apple/RNCWebView.h b/apple/RNCWebView.h index 0ce544db3..98c868ebd 100644 --- a/apple/RNCWebView.h +++ b/apple/RNCWebView.h @@ -71,6 +71,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *_Nonnull)request @property (nonatomic, copy) NSDictionary * _Nullable basicAuthCredential; @property (nonatomic, assign) BOOL pullToRefreshEnabled; @property (nonatomic, assign) BOOL enableApplePay; +@property (nonatomic, assign) NSInteger shouldStartLoadTimeout; @property (nonatomic, copy) NSArray * _Nullable menuItems; @property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection; #if !TARGET_OS_OSX diff --git a/apple/RNCWebViewManager.m b/apple/RNCWebViewManager.m index b5b76cbd3..54e2035f7 100644 --- a/apple/RNCWebViewManager.m +++ b/apple/RNCWebViewManager.m @@ -113,6 +113,8 @@ - (RCTUIView *)view RCT_EXPORT_VIEW_PROPERTY(menuItems, NSArray); RCT_EXPORT_VIEW_PROPERTY(onCustomMenuSelection, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(shouldStartLoadTimeout, NSInteger) + RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { @@ -254,8 +256,10 @@ - (BOOL) webView:(RNCWebView *)webView request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition); callback(request); - // Block the main thread for a maximum of 500ms until the JS thread returns - if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.50]]) { + NSInteger timeoutMs = webView.shouldStartLoadTimeout; + NSTimeInterval timeoutSeconds = timeoutMs / 1000.0; + + if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:timeoutSeconds]]) { BOOL returnValue = _shouldStartLoad; [_shouldStartLoadLock unlock]; _shouldStartLoadLock = nil; diff --git a/src/WebView.ios.tsx b/src/WebView.ios.tsx index 0189bdde2..e7d513ba1 100644 --- a/src/WebView.ios.tsx +++ b/src/WebView.ios.tsx @@ -74,7 +74,8 @@ const WebViewComponent = forwardRef<{}, IOSWebViewProps>((props, ref) => { cacheEnabled = true, originWhitelist = defaultOriginWhitelist, deeplinkWhitelist = defaultDeeplinkWhitelist, - textInteractionEnabled= true, + textInteractionEnabled = true, + shouldStartLoadTimeout = 500, injectedJavaScript, injectedJavaScriptBeforeContentLoaded, startInLoadingState, @@ -208,6 +209,7 @@ const WebViewComponent = forwardRef<{}, IOSWebViewProps>((props, ref) => { mediaPlaybackRequiresUserAction={mediaPlaybackRequiresUserAction} ref={webViewRef} sharedCookiesEnabled={sharedCookiesEnabled} + shouldStartLoadTimeout={shouldStartLoadTimeout} // TODO: find a better way to type this. source={source} style={webViewStyles} diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 0de7379d7..9bf26687c 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -321,6 +321,7 @@ export interface IOSNativeWebViewProps extends CommonNativeWebViewProps { enableApplePay?: boolean; textInteractionEnabled?: boolean; mediaCapturePermissionGrantType?: MediaCapturePermissionGrantType; + shouldStartLoadTimeout?: number; } export interface IOSWebViewProps extends WebViewSharedProps { @@ -573,6 +574,15 @@ export interface IOSWebViewProps extends WebViewSharedProps { * @platform ios */ onCustomMenuSelection?: (event: WebViewEvent) => void; + + /** + * Timeout in milliseconds for the shouldStartLoad callback to respond. + * If the JS thread is busy and cannot respond within this time, navigation + * will be blocked. + * The default value is `500`. + * @platform ios + */ + shouldStartLoadTimeout?: number; } export interface AndroidWebViewProps extends WebViewSharedProps {