diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index 1ad2a567e..2190ae714 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -13,6 +13,7 @@ import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; @@ -187,6 +188,7 @@ protected WebView createViewInstance(ThemedReactContext reactContext) { settings.setBuiltInZoomControls(true); settings.setDisplayZoomControls(false); settings.setDomStorageEnabled(true); + settings.setSupportMultipleWindows(true); settings.setAllowFileAccess(false); settings.setAllowContentAccess(false); @@ -252,6 +254,11 @@ public void setJavaScriptEnabled(WebView view, boolean enabled) { view.getSettings().setJavaScriptEnabled(enabled); } + @ReactProp(name = "setSupportMultipleWindows") + public void setSupportMultipleWindows(WebView view, boolean enabled){ + view.getSettings().setSupportMultipleWindows(enabled); + } + @ReactProp(name = "showsHorizontalScrollIndicator") public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) { view.setHorizontalScrollBarEnabled(enabled); @@ -875,7 +882,7 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand // This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request. String topWindowUrl = webView.getUrl(); String failingUrl = error.getUrl(); - + // Cancel request after obtaining top-level URL. // If request is cancelled before obtaining top-level URL, undesired behavior may occur. // Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL. @@ -1073,6 +1080,17 @@ public RNCWebChromeClient(ReactContext reactContext, WebView webView) { this.mWebView = webView; } + @Override + public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { + + final WebView newWebView = new WebView(view.getContext()); + final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(newWebView); + resultMsg.sendToTarget(); + + return true; + } + @Override public boolean onConsoleMessage(ConsoleMessage message) { if (ReactBuildConfig.DEBUG) { diff --git a/docs/Reference.md b/docs/Reference.md index f3ac6c5e3..bcd1dc3e3 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -72,6 +72,7 @@ This document lays out the current public properties and methods for the React N - [`ignoreSilentHardwareSwitch`](Reference.md#ignoreSilentHardwareSwitch) - [`onFileDownload`](Reference.md#onFileDownload) - [`autoManageStatusBarEnabled`](Reference.md#autoManageStatusBarEnabled) +- [`setSupportMultipleWindows`](Reference.md#setSupportMultipleWindows) ## Methods Index @@ -782,7 +783,7 @@ A Boolean value indicating whether JavaScript can open windows without user inte ### `androidHardwareAccelerationDisabled`[⬆](#props-index) -**Deprecated.** Use the `androidLayerType` prop instead. +**Deprecated.** Use the `androidLayerType` prop instead. | Type | Required | Platform | | ---- | -------- | -------- | @@ -792,7 +793,7 @@ A Boolean value indicating whether JavaScript can open windows without user inte ### `androidLayerType`[⬆](#props-index) -Specifies the layer type. +Specifies the layer type. Possible values for `androidLayerType` are: @@ -1282,6 +1283,21 @@ Example: ``` +### `setSupportMultipleWindows` + +Sets whether the WebView supports multiple windows. See [Android documentation]('https://developer.android.com/reference/android/webkit/WebSettings#setSupportMultipleWindows(boolean)') for more information. +Setting this to false can expose the application to this [vulnerability](https://alesandroortiz.com/articles/uxss-android-webview-cve-2020-6506/) allowing a malicious iframe to escape into the top layer DOM. + +| Type | Required | Default | Platform | +| ------- | -------- | ------- | -------- | +| boolean | No | true | Android | + +Example: + +```javascript + +``` + ## Methods ### `extraNativeComponentConfig()`[⬆](#methods-index) diff --git a/example/App.tsx b/example/App.tsx index d269439d6..b2d563465 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -18,6 +18,7 @@ import Uploads from './examples/Uploads'; import Injection from './examples/Injection'; import LocalPageLoad from './examples/LocalPageLoad'; import Messaging from './examples/Messaging'; +import NativeWebpage from './examples/NativeWebpage'; const TESTS = { Messaging: { @@ -84,6 +85,14 @@ const TESTS = { return ; }, }, + NativeWebpage: { + title: 'NativeWebpage', + testId: 'NativeWebpage', + description: 'Test to open a new webview with a link', + render() { + return ; + }, + }, }; type Props = {}; @@ -166,6 +175,11 @@ export default class App extends Component { title="Messaging" onPress={() => this._changeTest('Messaging')} /> +