From 7972fa0a76135df4acd1426bde9fe61670019121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Thu, 15 Jan 2026 14:18:01 +0100 Subject: [PATCH 1/4] feat: add suppressJavaScriptDialogs and allowFileDownloads props --- .../webview/RNCWebChromeClient.java | 32 +++++++++++++++++-- .../webview/RNCWebViewManagerImpl.kt | 28 ++++++++++++++-- .../webview/RNCWebViewManager.java | 14 +++++++- .../webview/RNCWebViewManager.java | 12 ++++++- apple/RNCWebView.mm | 1 + apple/RNCWebViewImpl.h | 1 + apple/RNCWebViewImpl.m | 12 +++++++ apple/RNCWebViewManager.mm | 1 + docs/Reference.md | 20 ++++++++++++ src/RNCWebViewNativeComponent.ts | 2 ++ src/WebViewTypes.ts | 14 ++++++++ 11 files changed, 130 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java index 7fda7cf05..c151ddb97 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java @@ -15,6 +15,7 @@ import android.view.ViewGroup; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; +import android.webkit.JsResult; import android.webkit.JsPromptResult; import android.webkit.PermissionRequest; import android.webkit.ValueCallback; @@ -62,6 +63,7 @@ public class RNCWebChromeClient extends WebChromeClient implements LifecycleEven // This boolean block JS prompts and alerts from displaying during loading protected boolean blockJsDuringLoading = true; + protected boolean suppressJavaScriptDialogs = false; /* * - Permissions - @@ -445,7 +447,7 @@ public boolean onShowFileChooser(WebView webView, ValueCallback filePathC @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - if (blockJsDuringLoading) { + if (shouldSuppressDialogs()) { result.cancel(); return true; } else { @@ -453,6 +455,24 @@ public boolean onJsPrompt(WebView view, String url, String message, String defau } } + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + if (shouldSuppressDialogs()) { + result.cancel(); + return true; + } + return super.onJsAlert(view, url, message, result); + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + if (shouldSuppressDialogs()) { + result.cancel(); + return true; + } + return super.onJsConfirm(view, url, message, result); + } + @Override public void onHostResume() { if (mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) { @@ -486,4 +506,12 @@ public void setAllowsProtectedMedia(boolean enabled) { public void setHasOnOpenWindowEvent(boolean hasEvent) { mHasOnOpenWindowEvent = hasEvent; } -} \ No newline at end of file + + public void setSuppressJavaScriptDialogs(boolean suppress) { + suppressJavaScriptDialogs = suppress; + } + + private boolean shouldSuppressDialogs() { + return suppressJavaScriptDialogs || blockJsDuringLoading; + } +} diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt index 335bc186e..ef82a2496 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt @@ -48,7 +48,9 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { private var mAllowsProtectedMedia = false private var mDownloadingMessage: String? = null private var mLackPermissionToDownloadMessage: String? = null + private var mAllowFileDownloads = true private var mHasOnOpenWindowEvent = false + private var mSuppressJavaScriptDialogs = false private var mPendingSource: ReadableMap? = null private var mUserAgent: String? = null @@ -102,9 +104,11 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { webView.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); } val base64DownloaderRequestFilePermission = { base64: String -> - webView.reactApplicationContext.getNativeModule(RNCWebViewModule::class.java)?.let { module -> - module.setBase64DownloadRequest(base64) - module.grantFileDownloaderPermissions(getDownloadingMessageOrDefault(), getLackPermissionToDownloadMessageOrDefault()) + if (mAllowFileDownloads) { + webView.reactApplicationContext.getNativeModule(RNCWebViewModule::class.java)?.let { module -> + module.setBase64DownloadRequest(base64) + module.grantFileDownloaderPermissions(getDownloadingMessageOrDefault(), getLackPermissionToDownloadMessageOrDefault()) + } } Unit } @@ -113,6 +117,9 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { requestFilePermission = base64DownloaderRequestFilePermission, ) webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> + if (!mAllowFileDownloads) { + return@DownloadListener + } if (url.startsWith("data:")) { Base64FileDownloader.downloadBase64File( context = context, @@ -255,6 +262,7 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { } webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia); webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent); + webChromeClient.setSuppressJavaScriptDialogs(mSuppressJavaScriptDialogs); webView.webChromeClient = webChromeClient } else { var webChromeClient = webView.webChromeClient as RNCWebChromeClient? @@ -266,6 +274,7 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { } webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia); webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent); + webChromeClient.setSuppressJavaScriptDialogs(mSuppressJavaScriptDialogs); webView.webChromeClient = webChromeClient } } @@ -673,6 +682,10 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { mLackPermissionToDownloadMessage = value } + fun setAllowFileDownloads(viewWrapper: RNCWebViewWrapper, value: Boolean) { + mAllowFileDownloads = value + } + fun setHasOnOpenWindowEvent(viewWrapper: RNCWebViewWrapper, value: Boolean) { val view = viewWrapper.webView mHasOnOpenWindowEvent = value @@ -698,6 +711,15 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { } } + fun setSuppressJavaScriptDialogs(viewWrapper: RNCWebViewWrapper, suppress: Boolean) { + val view = viewWrapper.webView + mSuppressJavaScriptDialogs = suppress + val client = view.webChromeClient + if (client is RNCWebChromeClient) { + client.setSuppressJavaScriptDialogs(suppress) + } + } + fun setMenuCustomItems(viewWrapper: RNCWebViewWrapper, value: ReadableArray?) { val view = viewWrapper.webView when (value) { diff --git a/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java index 93fe13d3a..6e21a020d 100644 --- a/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -207,12 +207,24 @@ public void setLackPermissionToDownloadMessage(RNCWebViewWrapper view, @Nullable mRNCWebViewManagerImpl.setLackPermissionToDownloadMessage(value); } + @Override + @ReactProp(name = "allowFileDownloads", defaultBoolean = true) + public void setAllowFileDownloads(RNCWebViewWrapper view, boolean value) { + mRNCWebViewManagerImpl.setAllowFileDownloads(view, value); + } + @Override @ReactProp(name = "hasOnOpenWindowEvent") public void setHasOnOpenWindowEvent(RNCWebViewWrapper view, boolean hasEvent) { mRNCWebViewManagerImpl.setHasOnOpenWindowEvent(view, hasEvent); } + @Override + @ReactProp(name = "suppressJavaScriptDialogs") + public void setSuppressJavaScriptDialogs(RNCWebViewWrapper view, boolean suppress) { + mRNCWebViewManagerImpl.setSuppressJavaScriptDialogs(view, suppress); + } + @Override @ReactProp(name = "mediaPlaybackRequiresUserAction") public void setMediaPlaybackRequiresUserAction(RNCWebViewWrapper view, boolean value) { @@ -556,4 +568,4 @@ public void onDropViewInstance(@NonNull RNCWebViewWrapper view) { mRNCWebViewManagerImpl.onDropViewInstance(view); super.onDropViewInstance(view); } -} \ No newline at end of file +} diff --git a/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java index 44d33783b..f6469cd7c 100644 --- a/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -168,11 +168,21 @@ public void setLackPermissionToDownloadMessage(RNCWebViewWrapper view, @Nullable mRNCWebViewManagerImpl.setLackPermissionToDownloadMessage(value); } + @ReactProp(name = "allowFileDownloads", defaultBoolean = true) + public void setAllowFileDownloads(RNCWebViewWrapper view, boolean value) { + mRNCWebViewManagerImpl.setAllowFileDownloads(view, value); + } + @ReactProp(name = "hasOnOpenWindowEvent") public void setHasOnOpenWindowEvent(RNCWebViewWrapper view, boolean hasEvent) { mRNCWebViewManagerImpl.setHasOnOpenWindowEvent(view, hasEvent); } + @ReactProp(name = "suppressJavaScriptDialogs") + public void setSuppressJavaScriptDialogs(RNCWebViewWrapper view, boolean suppress) { + mRNCWebViewManagerImpl.setSuppressJavaScriptDialogs(view, suppress); + } + @ReactProp(name = "mediaPlaybackRequiresUserAction") public void setMediaPlaybackRequiresUserAction(RNCWebViewWrapper view, boolean value) { mRNCWebViewManagerImpl.setMediaPlaybackRequiresUserAction(view, value); @@ -330,4 +340,4 @@ public void onDropViewInstance(@NonNull RNCWebViewWrapper view) { mRNCWebViewManagerImpl.onDropViewInstance(view); super.onDropViewInstance(view); } -} \ No newline at end of file +} diff --git a/apple/RNCWebView.mm b/apple/RNCWebView.mm index f9d080e3d..e7ca571f0 100644 --- a/apple/RNCWebView.mm +++ b/apple/RNCWebView.mm @@ -277,6 +277,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & REMAP_WEBVIEW_STRING_PROP(injectedJavaScriptObject) REMAP_WEBVIEW_PROP(javaScriptEnabled) REMAP_WEBVIEW_PROP(javaScriptCanOpenWindowsAutomatically) + REMAP_WEBVIEW_PROP(suppressJavaScriptDialogs) REMAP_WEBVIEW_PROP(allowFileAccessFromFileURLs) REMAP_WEBVIEW_PROP(allowUniversalAccessFromFileURLs) REMAP_WEBVIEW_PROP(allowsInlineMediaPlayback) diff --git a/apple/RNCWebViewImpl.h b/apple/RNCWebViewImpl.h index 6246c45ef..49bbb3694 100644 --- a/apple/RNCWebViewImpl.h +++ b/apple/RNCWebViewImpl.h @@ -97,6 +97,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request @property (nonatomic, assign) BOOL cacheEnabled; @property (nonatomic, assign) BOOL javaScriptEnabled; @property (nonatomic, assign) BOOL javaScriptCanOpenWindowsAutomatically; +@property (nonatomic, assign) BOOL suppressJavaScriptDialogs; @property (nonatomic, assign) BOOL allowFileAccessFromFileURLs; @property (nonatomic, assign) BOOL allowUniversalAccessFromFileURLs; @property (nonatomic, assign) BOOL allowsLinkPreview; diff --git a/apple/RNCWebViewImpl.m b/apple/RNCWebViewImpl.m index ebbb771d6..46c6ebfef 100644 --- a/apple/RNCWebViewImpl.m +++ b/apple/RNCWebViewImpl.m @@ -1268,6 +1268,10 @@ - (void) webView:(WKWebView *)webView */ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler { + if (_suppressJavaScriptDialogs) { + completionHandler(); + return; + } #if !TARGET_OS_OSX UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { @@ -1287,6 +1291,10 @@ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSStrin * confirm */ - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{ + if (_suppressJavaScriptDialogs) { + completionHandler(NO); + return; + } #if !TARGET_OS_OSX UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { @@ -1312,6 +1320,10 @@ - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSStr * prompt */ - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{ + if (_suppressJavaScriptDialogs) { + completionHandler(nil); + return; + } if (!_disablePromptDuringLoading) { #if !TARGET_OS_OSX UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert]; diff --git a/apple/RNCWebViewManager.mm b/apple/RNCWebViewManager.mm index a0e0d0063..96855c857 100644 --- a/apple/RNCWebViewManager.mm +++ b/apple/RNCWebViewManager.mm @@ -59,6 +59,7 @@ - (RNCView *)view RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptObject, NSString) RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(javaScriptCanOpenWindowsAutomatically, BOOL) +RCT_EXPORT_VIEW_PROPERTY(suppressJavaScriptDialogs, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowUniversalAccessFromFileURLs, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL) diff --git a/docs/Reference.md b/docs/Reference.md index 3d37b74cf..ec934cb7e 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -38,6 +38,7 @@ This document lays out the current public properties and methods for the React N - [`domStorageEnabled`](Reference.md#domstorageenabled) - [`javaScriptEnabled`](Reference.md#javascriptenabled) - [`javaScriptCanOpenWindowsAutomatically`](Reference.md#javascriptcanopenwindowsautomatically) +- [`suppressJavaScriptDialogs`](Reference.md#suppressjavascriptdialogs) - [`androidLayerType`](Reference.md#androidLayerType) - [`mixedContentMode`](Reference.md#mixedcontentmode) - [`thirdPartyCookiesEnabled`](Reference.md#thirdpartycookiesenabled) @@ -91,6 +92,7 @@ This document lays out the current public properties and methods for the React N - [`minimumFontSize`](Reference.md#minimumFontSize) - [`downloadingMessage`](Reference.md#downloadingMessage) - [`lackPermissionToDownloadMessage`](Reference.md#lackPermissionToDownloadMessage) +- [`allowFileDownloads`](Reference.md#allowfiledownloads) - [`allowsProtectedMedia`](Reference.md#allowsProtectedMedia) - [`webviewDebuggingEnabled`](Reference.md#webviewDebuggingEnabled) - [`paymentRequestEnabled`](Reference.md#paymentRequestEnabled) @@ -926,6 +928,16 @@ A Boolean value indicating whether JavaScript can open windows without user inte --- +### `suppressJavaScriptDialogs`[⬆](#props-index) + +Boolean value to suppress JavaScript dialogs (alert/confirm/prompt). The default value is `false`. + +| Type | Required | Platform | +| ---- | -------- | -------- | +| bool | No | Android, iOS | + +--- + ### `androidLayerType`[⬆](#props-index) Specifies the layer type. @@ -1693,6 +1705,14 @@ This is the message that is shown in the Toast when the webview is unable to dow | ------ | -------- | -------- | | string | No | Android | +### `allowFileDownloads`[⬆](#props-index) + +Boolean value to control whether file downloads are allowed. The default value is `true`. + +| Type | Required | Platform | +| ---- | -------- | -------- | +| bool | No | Android | + ### `allowsProtectedMedia`[⬆](#props-index) Whether or not the Webview can play media protected by DRM. Default is `false`. diff --git a/src/RNCWebViewNativeComponent.ts b/src/RNCWebViewNativeComponent.ts index 3e774ef19..6fe64b469 100644 --- a/src/RNCWebViewNativeComponent.ts +++ b/src/RNCWebViewNativeComponent.ts @@ -160,6 +160,7 @@ export interface NativeProps extends ViewProps { forceDarkOn?: boolean; geolocationEnabled?: boolean; lackPermissionToDownloadMessage?: string; + allowFileDownloads?: boolean; messagingModuleName: string; minimumFontSize?: Int32; mixedContentMode?: WithDefault<'never' | 'always' | 'compatibility', 'never'>; @@ -265,6 +266,7 @@ export interface NativeProps extends ViewProps { true >; javaScriptCanOpenWindowsAutomatically?: boolean; + suppressJavaScriptDialogs?: boolean; javaScriptEnabled?: WithDefault; webviewDebuggingEnabled?: boolean; mediaPlaybackRequiresUserAction?: WithDefault; diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 3f29ddd6c..31d7aaef5 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -1140,6 +1140,13 @@ export interface AndroidWebViewProps extends WebViewSharedProps { */ lackPermissionToDownloadMessage?: string; + /** + * Boolean value to control whether file downloads are allowed. + * The default value is `true`. + * @platform android + */ + allowFileDownloads?: boolean; + /** * Boolean value to control whether webview can play media protected by DRM. * Default is false. @@ -1167,6 +1174,13 @@ export interface WebViewSharedProps extends ViewProps { */ javaScriptCanOpenWindowsAutomatically?: boolean; + /** + * Boolean value to suppress JavaScript dialogs (alert/confirm/prompt). + * The default value is `false`. + * @platform android, ios + */ + suppressJavaScriptDialogs?: boolean; + /** * Stylesheet object to set the style of the container view. */ From 2b13ad53303f8c62c4c7d53b83965b18bc3b27b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Thu, 15 Jan 2026 14:18:29 +0100 Subject: [PATCH 2/4] chore: add examples for suppressJavaScriptDialogs and allowFileDownloads --- example/examples/Alerts.tsx | 76 +++++++++++++++++++++++++------ example/examples/Downloads.tsx | 81 ++++++++++++++++++++++++---------- example/tsconfig.json | 11 +++++ metro.config.js | 1 + 4 files changed, 131 insertions(+), 38 deletions(-) create mode 100644 example/tsconfig.json diff --git a/example/examples/Alerts.tsx b/example/examples/Alerts.tsx index 61b7e48dd..d28c89a57 100644 --- a/example/examples/Alerts.tsx +++ b/example/examples/Alerts.tsx @@ -1,5 +1,5 @@ -import React, {Component} from 'react'; -import {Text, View} from 'react-native'; +import React, { useState } from 'react'; +import { StyleSheet, Switch, Text, View } from 'react-native'; import WebView from '@metamask/react-native-webview'; @@ -13,17 +13,23 @@ const HTML = ` -

+

Result will appear here...