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/RNCWebView.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java index 3aaf6957e..3f9aa9c99 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java @@ -81,6 +81,8 @@ public class RNCWebView extends WebView implements LifecycleEventListener { protected boolean hasScrollEvent = false; protected boolean nestedScrollEnabled = false; protected ProgressChangedFilter progressChangedFilter; + protected boolean allowFileDownloads = true; + protected boolean suppressJavaScriptDialogs = false; /** Samsung Manufacturer Name */ private static final String SAMSUNG_MANUFACTURER_NAME = "samsung"; @@ -121,6 +123,22 @@ public void setNestedScrollEnabled(boolean nestedScrollEnabled) { this.nestedScrollEnabled = nestedScrollEnabled; } + public void setAllowFileDownloads(boolean allow) { + this.allowFileDownloads = allow; + } + + public boolean getAllowFileDownloads() { + return this.allowFileDownloads; + } + + public void setSuppressJavaScriptDialogs(boolean suppress) { + this.suppressJavaScriptDialogs = suppress; + } + + public boolean getSuppressJavaScriptDialogs() { + return this.suppressJavaScriptDialogs; + } + @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { InputConnection inputConnection; diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt index 335bc186e..b19f45615 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt @@ -102,9 +102,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 (webView.allowFileDownloads) { + webView.reactApplicationContext.getNativeModule(RNCWebViewModule::class.java)?.let { module -> + module.setBase64DownloadRequest(base64) + module.grantFileDownloaderPermissions(getDownloadingMessageOrDefault(), getLackPermissionToDownloadMessageOrDefault()) + } } Unit } @@ -113,6 +115,9 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { requestFilePermission = base64DownloaderRequestFilePermission, ) webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> + if (!webView.allowFileDownloads) { + return@DownloadListener + } if (url.startsWith("data:")) { Base64FileDownloader.downloadBase64File( context = context, @@ -255,6 +260,7 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { } webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia); webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent); + webChromeClient.setSuppressJavaScriptDialogs(webView.suppressJavaScriptDialogs); webView.webChromeClient = webChromeClient } else { var webChromeClient = webView.webChromeClient as RNCWebChromeClient? @@ -266,6 +272,7 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { } webChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia); webChromeClient.setHasOnOpenWindowEvent(mHasOnOpenWindowEvent); + webChromeClient.setSuppressJavaScriptDialogs(webView.suppressJavaScriptDialogs); webView.webChromeClient = webChromeClient } } @@ -673,6 +680,11 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { mLackPermissionToDownloadMessage = value } + fun setAllowFileDownloads(viewWrapper: RNCWebViewWrapper, value: Boolean) { + val view = viewWrapper.webView + view.allowFileDownloads = value + } + fun setHasOnOpenWindowEvent(viewWrapper: RNCWebViewWrapper, value: Boolean) { val view = viewWrapper.webView mHasOnOpenWindowEvent = value @@ -698,6 +710,15 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { } } + fun setSuppressJavaScriptDialogs(viewWrapper: RNCWebViewWrapper, suppress: Boolean) { + val view = viewWrapper.webView + view.suppressJavaScriptDialogs = 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/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt b/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt index 0ad6183e6..f338a6a4e 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt +++ b/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt @@ -1,6 +1,5 @@ package com.reactnativecommunity.webview.extension.file -import android.content.Context import android.webkit.JavascriptInterface import com.reactnativecommunity.webview.RNCWebView import com.reactnativecommunity.webview.extension.file.Base64FileDownloader.downloadBase64File @@ -8,13 +7,13 @@ import java.io.IOException internal fun RNCWebView.addBlobFileDownloaderJavascriptInterface(downloadingMessage: String, requestFilePermission: (String) -> Unit) { this.addJavascriptInterface( - BlobFileDownloader(this.context, downloadingMessage, requestFilePermission), + BlobFileDownloader(this, downloadingMessage, requestFilePermission), BlobFileDownloader.JS_INTERFACE_TAG, ) } internal class BlobFileDownloader( - private val context: Context, + private val webView: RNCWebView, private val downloadingMessage: String, private val requestFilePermission: (String) -> Unit, ) { @@ -22,7 +21,10 @@ internal class BlobFileDownloader( @JavascriptInterface @Throws(IOException::class) fun getBase64FromBlobData(base64: String) { - downloadBase64File(context, base64, downloadingMessage, requestFilePermission) + if (!webView.allowFileDownloads) { + return + } + downloadBase64File(webView.context, base64, downloadingMessage, requestFilePermission) } companion object { 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/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...