Skip to content

Commit e91396a

Browse files
authored
Merge pull request #16 from phantom/kuba/wp-7127-cross-tab-javascript-execution-in-in-app-browser-enables/webview
feat: allow passing `active` prop that allows preventing inactive webivews from showing JS alerts
2 parents 6f2b256 + eaabb30 commit e91396a

File tree

11 files changed

+92
-1
lines changed

11 files changed

+92
-1
lines changed

.changeset/huge-onions-lie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@phantom/react-native-webview': minor
3+
---
4+
5+
Added a new prop that allows marking webviews as active or inactive.

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import android.view.ViewGroup;
1717
import android.webkit.ConsoleMessage;
1818
import android.webkit.GeolocationPermissions;
19+
import android.webkit.JsPromptResult;
20+
import android.webkit.JsResult;
1921
import android.webkit.PermissionRequest;
2022
import android.webkit.ValueCallback;
2123
import android.webkit.WebChromeClient;
@@ -479,4 +481,34 @@ public void setAllowsProtectedMedia(boolean enabled) {
479481
public void setHasOnOpenWindowEvent(boolean hasEvent) {
480482
mHasOnOpenWindowEvent = hasEvent;
481483
}
482-
}
484+
485+
/**
486+
* Security: Prevent dialogs from being presented when webview is inactive.
487+
*/
488+
@Override
489+
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
490+
if (!mWebView.isActive()) {
491+
result.cancel();
492+
return true; // Consumed - don't show default dialog
493+
}
494+
return false; // Show default system dialog
495+
}
496+
497+
@Override
498+
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
499+
if (!mWebView.isActive()) {
500+
result.cancel();
501+
return true;
502+
}
503+
return false;
504+
}
505+
506+
@Override
507+
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
508+
if (!mWebView.isActive()) {
509+
result.cancel();
510+
return true;
511+
}
512+
return false;
513+
}
514+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class RNCWebView extends WebView implements LifecycleEventListener {
7878
protected boolean hasScrollEvent = false;
7979
protected boolean nestedScrollEnabled = false;
8080
protected ProgressChangedFilter progressChangedFilter;
81+
protected boolean mActive = true;
8182

8283
/**
8384
* WebView must be created with an context of the current activity
@@ -107,6 +108,14 @@ public void setNestedScrollEnabled(boolean nestedScrollEnabled) {
107108
this.nestedScrollEnabled = nestedScrollEnabled;
108109
}
109110

111+
public void setActive(boolean active) {
112+
this.mActive = active;
113+
}
114+
115+
public boolean isActive() {
116+
return this.mActive;
117+
}
118+
110119
@Override
111120
public void onHostResume() {
112121
// do nothing

android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,4 +681,8 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
681681
WebSettingsCompat.setPaymentRequestEnabled(view.settings, enabled)
682682
}
683683
}
684+
685+
fun setActive(viewWrapper: RNCWebViewWrapper, value: Boolean) {
686+
viewWrapper.webView.setActive(value)
687+
}
684688
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,12 @@ public void setMediaCapturePermissionGrantType(RNCWebViewWrapper view, @Nullable
430430
public void setFraudulentWebsiteWarningEnabled(RNCWebViewWrapper view, boolean value) {}
431431
/* !iOS PROPS - no implemented here */
432432

433+
@Override
434+
@ReactProp(name = "active")
435+
public void setActive(RNCWebViewWrapper view, boolean value) {
436+
mRNCWebViewManagerImpl.setActive(view, value);
437+
}
438+
433439
@Override
434440
@ReactProp(name = "userAgent")
435441
public void setUserAgent(RNCWebViewWrapper view, @Nullable String value) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,11 @@ public void setPaymentRequestEnabled(RNCWebViewWrapper view, boolean value) {
279279
mRNCWebViewManagerImpl.setPaymentRequestEnabled(view, value);
280280
}
281281

282+
@ReactProp(name = "active")
283+
public void setActive(RNCWebViewWrapper view, boolean value) {
284+
mRNCWebViewManagerImpl.setActive(view, value);
285+
}
286+
282287
@Override
283288
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, RNCWebViewWrapper viewWrapper) {
284289
// Do not register default touch emitter and let WebView implementation handle touches

apple/RNCWebViewImpl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
113113
@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems;
114114
@property (nonatomic, copy) NSArray<NSString *> * _Nullable suppressMenuItems;
115115
@property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection;
116+
@property (nonatomic, assign) BOOL active;
116117
#if !TARGET_OS_OSX
117118
@property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes;
118119
@property (nonatomic, weak) UIRefreshControl * _Nullable refreshControl;

apple/RNCWebViewImpl.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ - (instancetype)initWithFrame:(CGRect)frame
176176
_autoManageStatusBarEnabled = YES;
177177
_contentInset = UIEdgeInsetsZero;
178178
_savedKeyboardDisplayRequiresUserAction = YES;
179+
_active = YES;
179180
_injectedJavaScript = nil;
180181
_injectedJavaScriptForMainFrameOnly = YES;
181182
_injectedJavaScriptBeforeContentLoaded = nil;
@@ -1203,6 +1204,11 @@ - (void) webView:(WKWebView *)webView
12031204
*/
12041205
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
12051206
{
1207+
// Security: Suppress dialogs from non-active tabs to prevent cross-tab spoofing
1208+
if (!_active) {
1209+
completionHandler();
1210+
return;
1211+
}
12061212
#if !TARGET_OS_OSX
12071213
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
12081214
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
@@ -1222,6 +1228,11 @@ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSStrin
12221228
* confirm
12231229
*/
12241230
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
1231+
// Security: Suppress dialogs from non-active tabs to prevent cross-tab spoofing
1232+
if (!_active) {
1233+
completionHandler(NO);
1234+
return;
1235+
}
12251236
#if !TARGET_OS_OSX
12261237
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
12271238
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
@@ -1247,6 +1258,11 @@ - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSStr
12471258
* prompt
12481259
*/
12491260
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
1261+
// Security: Suppress dialogs from non-active tabs to prevent cross-tab spoofing
1262+
if (!_active) {
1263+
completionHandler(nil);
1264+
return;
1265+
}
12501266
#if !TARGET_OS_OSX
12511267
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
12521268
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {

apple/RNCWebViewManager.mm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ - (RNCView *)view
125125
RCT_CUSTOM_VIEW_PROPERTY(pullToRefreshEnabled, BOOL, RNCWebViewImpl) {
126126
view.pullToRefreshEnabled = json == nil ? false : [RCTConvert BOOL: json];
127127
}
128+
RCT_CUSTOM_VIEW_PROPERTY(active, BOOL, RNCWebViewImpl) {
129+
view.active = json == nil ? YES : [RCTConvert BOOL: json];
130+
}
128131
RCT_CUSTOM_VIEW_PROPERTY(refreshControlLightMode, BOOL, RNCWebViewImpl) {
129132
view.refreshControlLightMode = json == nil ? false : [RCTConvert BOOL: json];
130133
}

src/RNCWebViewNativeComponent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ export interface NativeProps extends ViewProps {
296296
userAgent?: string;
297297
injectedJavaScriptObject?: string;
298298
paymentRequestEnabled?: boolean;
299+
active?: WithDefault<boolean, true>;
299300
}
300301

301302
export interface NativeCommands {

0 commit comments

Comments
 (0)