diff --git a/.changeset/huge-onions-lie.md b/.changeset/huge-onions-lie.md new file mode 100644 index 0000000000..7ae9cdbadc --- /dev/null +++ b/.changeset/huge-onions-lie.md @@ -0,0 +1,5 @@ +--- +'@phantom/react-native-webview': minor +--- + +Added a new prop that allows marking webviews as active or inactive. diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java index bd2efa5c41..7c0131811c 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebChromeClient.java @@ -16,6 +16,8 @@ import android.view.ViewGroup; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; @@ -479,4 +481,34 @@ public void setAllowsProtectedMedia(boolean enabled) { public void setHasOnOpenWindowEvent(boolean hasEvent) { mHasOnOpenWindowEvent = hasEvent; } -} \ No newline at end of file + + /** + * Security: Prevent dialogs from being presented when webview is inactive. + */ + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + if (!mWebView.isActive()) { + result.cancel(); + return true; // Consumed - don't show default dialog + } + return false; // Show default system dialog + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + if (!mWebView.isActive()) { + result.cancel(); + return true; + } + return false; + } + + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { + if (!mWebView.isActive()) { + result.cancel(); + return true; + } + return false; + } +} diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java index 486c77a060..7a2cc3c663 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java @@ -78,6 +78,7 @@ public class RNCWebView extends WebView implements LifecycleEventListener { protected boolean hasScrollEvent = false; protected boolean nestedScrollEnabled = false; protected ProgressChangedFilter progressChangedFilter; + protected boolean mActive = true; /** * WebView must be created with an context of the current activity @@ -107,6 +108,14 @@ public void setNestedScrollEnabled(boolean nestedScrollEnabled) { this.nestedScrollEnabled = nestedScrollEnabled; } + public void setActive(boolean active) { + this.mActive = active; + } + + public boolean isActive() { + return this.mActive; + } + @Override public void onHostResume() { // do nothing diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt index 3e18463562..ebfd70763c 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt @@ -681,4 +681,8 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) { WebSettingsCompat.setPaymentRequestEnabled(view.settings, enabled) } } + + fun setActive(viewWrapper: RNCWebViewWrapper, value: Boolean) { + viewWrapper.webView.setActive(value) + } } diff --git a/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java index 4709e28224..53c40af53f 100644 --- a/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/newarch/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -430,6 +430,12 @@ public void setMediaCapturePermissionGrantType(RNCWebViewWrapper view, @Nullable public void setFraudulentWebsiteWarningEnabled(RNCWebViewWrapper view, boolean value) {} /* !iOS PROPS - no implemented here */ + @Override + @ReactProp(name = "active") + public void setActive(RNCWebViewWrapper view, boolean value) { + mRNCWebViewManagerImpl.setActive(view, value); + } + @Override @ReactProp(name = "userAgent") public void setUserAgent(RNCWebViewWrapper view, @Nullable String value) { diff --git a/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java index 974337f41c..27b7a03d3a 100644 --- a/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -279,6 +279,11 @@ public void setPaymentRequestEnabled(RNCWebViewWrapper view, boolean value) { mRNCWebViewManagerImpl.setPaymentRequestEnabled(view, value); } + @ReactProp(name = "active") + public void setActive(RNCWebViewWrapper view, boolean value) { + mRNCWebViewManagerImpl.setActive(view, value); + } + @Override protected void addEventEmitters(@NonNull ThemedReactContext reactContext, RNCWebViewWrapper viewWrapper) { // Do not register default touch emitter and let WebView implementation handle touches diff --git a/apple/RNCWebViewImpl.h b/apple/RNCWebViewImpl.h index 1f6bbfd697..01198d5b12 100644 --- a/apple/RNCWebViewImpl.h +++ b/apple/RNCWebViewImpl.h @@ -113,6 +113,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request @property (nonatomic, copy) NSArray * _Nullable menuItems; @property (nonatomic, copy) NSArray * _Nullable suppressMenuItems; @property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection; +@property (nonatomic, assign) BOOL active; #if !TARGET_OS_OSX @property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes; @property (nonatomic, weak) UIRefreshControl * _Nullable refreshControl; diff --git a/apple/RNCWebViewImpl.m b/apple/RNCWebViewImpl.m index 7733e1b149..5fbe05b7ba 100644 --- a/apple/RNCWebViewImpl.m +++ b/apple/RNCWebViewImpl.m @@ -176,6 +176,7 @@ - (instancetype)initWithFrame:(CGRect)frame _autoManageStatusBarEnabled = YES; _contentInset = UIEdgeInsetsZero; _savedKeyboardDisplayRequiresUserAction = YES; + _active = YES; _injectedJavaScript = nil; _injectedJavaScriptForMainFrameOnly = YES; _injectedJavaScriptBeforeContentLoaded = nil; @@ -1203,6 +1204,11 @@ - (void) webView:(WKWebView *)webView */ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler { + // Security: Suppress dialogs from non-active tabs to prevent cross-tab spoofing + if (!_active) { + 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) { @@ -1222,6 +1228,11 @@ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSStrin * confirm */ - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{ + // Security: Suppress dialogs from non-active tabs to prevent cross-tab spoofing + if (!_active) { + 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) { @@ -1247,6 +1258,11 @@ - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSStr * prompt */ - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{ + // Security: Suppress dialogs from non-active tabs to prevent cross-tab spoofing + if (!_active) { + completionHandler(nil); + return; + } #if !TARGET_OS_OSX UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert]; [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { diff --git a/apple/RNCWebViewManager.mm b/apple/RNCWebViewManager.mm index f8f375f23b..0ae8e24f54 100644 --- a/apple/RNCWebViewManager.mm +++ b/apple/RNCWebViewManager.mm @@ -125,6 +125,9 @@ - (RNCView *)view RCT_CUSTOM_VIEW_PROPERTY(pullToRefreshEnabled, BOOL, RNCWebViewImpl) { view.pullToRefreshEnabled = json == nil ? false : [RCTConvert BOOL: json]; } +RCT_CUSTOM_VIEW_PROPERTY(active, BOOL, RNCWebViewImpl) { + view.active = json == nil ? YES : [RCTConvert BOOL: json]; +} RCT_CUSTOM_VIEW_PROPERTY(refreshControlLightMode, BOOL, RNCWebViewImpl) { view.refreshControlLightMode = json == nil ? false : [RCTConvert BOOL: json]; } diff --git a/src/RNCWebViewNativeComponent.ts b/src/RNCWebViewNativeComponent.ts index cb9a9ceffd..ff9ee501ff 100644 --- a/src/RNCWebViewNativeComponent.ts +++ b/src/RNCWebViewNativeComponent.ts @@ -296,6 +296,7 @@ export interface NativeProps extends ViewProps { userAgent?: string; injectedJavaScriptObject?: string; paymentRequestEnabled?: boolean; + active?: WithDefault; } export interface NativeCommands { diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index c5cf7ff749..1e2dadd0d0 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -1358,4 +1358,13 @@ export interface WebViewSharedProps extends ViewProps { * Enables support for the Payment Request API for the WebView */ paymentRequestEnabled?: boolean; + + /** + * Allows marking the WebView as active or inactive. + * When a WebView is marked inactive, JS dialogs and alerts will be blocked and won't be shown to the user + * @platform android + * @platform ios + * @default true + */ + active?: boolean; }