Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2b28b44
chore: correct spelling error in WebView documentation (#3771)
dongtony12 May 29, 2025
f66ecfd
chore: Update JSDOC for clearCache (#3748)
flodaniel May 29, 2025
159e207
chore: Improve domStorageEnabled documentation (#3769)
yenshirak May 29, 2025
6af6a83
feat(ios): Added support for scrollview indicatorStyle (#3743)
animaonline Jun 1, 2025
7ba2b4b
chore(release): 13.14.0 [skip ci]
semantic-release-bot Jun 1, 2025
823e6ae
fix: support react-native-codegen properly (#3777)
Titozzz Jun 2, 2025
87b2265
chore(release): 13.14.1 [skip ci]
semantic-release-bot Jun 2, 2025
5a73948
fix(android): suppressMenuItems native view config name with extra sp…
shwanton Jun 7, 2025
3b5d989
chore(release): 13.14.2 [skip ci]
semantic-release-bot Jun 7, 2025
4ea6d6b
feat(android): add support for enabling the Payment Request API (#227…
dmengelt Jun 8, 2025
94beed4
chore(release): 13.15.0 [skip ci]
semantic-release-bot Jun 8, 2025
56989ca
fix(android): revert #1221 setIgnoreErrFailedForThisURL (#3831)
coolsoftwaretyler Aug 25, 2025
8b96334
feat(android): Add sub resource SSL error handling on Android (#3834)
coolsoftwaretyler Aug 25, 2025
2ee4218
fix(ios): Inline CPP operator to avoid duplicate symbols during linki…
stigi Aug 25, 2025
dd6fb80
fix(types): Make `clearCache` method required instead of optional (#3…
alphatrl Aug 25, 2025
5bc526f
chore(release): 13.16.0 [skip ci]
semantic-release-bot Aug 25, 2025
ed16c9b
Merge upstream/master - upgrade androidx.webkit:webkit to 1.14.0
misha-phantom Sep 8, 2025
fd371fc
chore: update iOS CI configuration and Podfile.lock
misha-phantom Sep 8, 2025
a08be33
add changeset
misha-phantom Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/big-files-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@phantom/react-native-webview': minor
---

- Merged 16 commits from upstream/master
- Upgraded androidx.webkit:webkit from 1.4.0 to 1.14.0
- Added SSL error handling for sub-resources
- Added Payment Request API support (disabled downloads for security)
- Preserved Phantom's custom changes:
- Package name and version (1.0.2)
- Download blocking with toast message
- All existing security configurations
2 changes: 1 addition & 1 deletion .github/workflows/ios-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
working-directory: example/ios
- name: Build iOS test app
run: |
device_name='iPhone 15'
device_name='iPhone 16'
device=$(xcrun simctl list devices "${device_name}" available | grep "${device_name} (")
re='\(([-0-9A-Fa-f]+)\)'
[[ $device =~ $re ]] || exit 1
Expand Down
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ReactNativeWebView_kotlinVersion=1.6.0
ReactNativeWebView_webkitVersion=1.4.0
ReactNativeWebView_webkitVersion=1.14.0
ReactNativeWebView_compileSdkVersion=31
ReactNativeWebView_targetSdkVersion=31
ReactNativeWebView_minSdkVersion=24
12 changes: 12 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativecommunity.webview">

<queries>
<intent>
<action android:name="org.chromium.intent.action.PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.IS_READY_TO_PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS"/>
</intent>
</queries>

<application>
<provider
android:name=".RNCWebViewFileProvider"
Expand Down
13 changes: 13 additions & 0 deletions android/src/main/AndroidManifestNew.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<queries>
<intent>
<action android:name="org.chromium.intent.action.PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.IS_READY_TO_PAY"/>
</intent>
<intent>
<action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS"/>
</intent>
</queries>

<application>
<provider
android:name=".RNCWebViewFileProvider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@ public RNCWebView(ThemedReactContext reactContext) {
progressChangedFilter = new ProgressChangedFilter();
}

public void setIgnoreErrFailedForThisURL(String url) {
mRNCWebViewClient.setIgnoreErrFailedForThisURL(url);
}

public void setBasicAuthCredential(RNCBasicAuthCredential credential) {
mRNCWebViewClient.setBasicAuthCredential(credential);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
Expand All @@ -42,13 +43,8 @@ public class RNCWebViewClient extends WebViewClient {

protected boolean mLastLoadFailed = false;
protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
protected @Nullable String ignoreErrFailedForThisURL = null;
protected @Nullable RNCBasicAuthCredential basicAuthCredential = null;

public void setIgnoreErrFailedForThisURL(@Nullable String url) {
ignoreErrFailedForThisURL = url;
}

public void setBasicAuthCredential(@Nullable RNCBasicAuthCredential credential) {
basicAuthCredential = credential;
}
Expand Down Expand Up @@ -173,12 +169,6 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand
// Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
handler.cancel();

if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
// If error is not due to top-level navigation, then do not call onReceivedError()
Log.w(TAG, "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
return;
}

int code = error.getPrimaryError();
String description = "";
String descriptionPrefix = "SSL error: ";
Expand Down Expand Up @@ -210,6 +200,18 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand

description = descriptionPrefix + description;

if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
// If error is not due to top-level navigation, then do not call onReceivedError()
Log.w(TAG, "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
this.onReceivedSubResourceSslError(
webView,
code,
description,
failingUrl
);
return;
}

this.onReceivedError(
webView,
code,
Expand All @@ -218,27 +220,27 @@ public void onReceivedSslError(final WebView webView, final SslErrorHandler hand
);
}

public void onReceivedSubResourceSslError(
WebView webView,
int errorCode,
String description,
String failingUrl) {

WritableMap eventData = createWebViewEvent(webView, failingUrl);
eventData.putDouble("code", errorCode);
eventData.putString("description", description);

int reactTag = RNCWebViewWrapper.getReactTagFromWebView(webView);
UIManagerHelper.getEventDispatcherForReactTag((ReactContext) webView.getContext(), reactTag).dispatchEvent(new SubResourceErrorEvent(reactTag, eventData));
}

@Override
public void onReceivedError(
WebView webView,
int errorCode,
String description,
String failingUrl) {

if (ignoreErrFailedForThisURL != null
&& failingUrl.equals(ignoreErrFailedForThisURL)
&& errorCode == -1
&& description.equals("net::ERR_FAILED")) {

// This is a workaround for a bug in the WebView.
// See these chromium issues for more context:
// https://bugs.chromium.org/p/chromium/issues/detail?id=1023678
// https://bugs.chromium.org/p/chromium/issues/detail?id=1050635
// This entire commit should be reverted once this bug is resolved in chromium.
setIgnoreErrFailedForThisURL(null);
return;
}

super.onReceivedError(webView, errorCode, description, failingUrl);
mLastLoadFailed = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
WebView.setWebContentsDebuggingEnabled(true)
}
webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
webView.setIgnoreErrFailedForThisURL(url)
android.widget.Toast.makeText(context, "File downloads are not supported", android.widget.Toast.LENGTH_SHORT).show();
return@DownloadListener;
})
Expand Down Expand Up @@ -675,4 +674,11 @@ class RNCWebViewManagerImpl(private val newArch: Boolean = false) {
fun setWebviewDebuggingEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
RNCWebView.setWebContentsDebuggingEnabled(enabled)
}

fun setPaymentRequestEnabled(viewWrapper: RNCWebViewWrapper, enabled: Boolean) {
val view = viewWrapper.webView
if (WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) {
WebSettingsCompat.setPaymentRequestEnabled(view.settings, enabled)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.reactnativecommunity.webview.events

import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter

/**
* Event emitted when there is an error in loading a subresource
*/
class SubResourceErrorEvent(viewId: Int, private val mEventData: WritableMap) :
Event<SubResourceErrorEvent>(viewId) {
companion object {
const val EVENT_NAME = "topLoadingSubResourceError"
}

override fun getEventName(): String = EVENT_NAME

override fun canCoalesce(): Boolean = false

override fun getCoalescingKey(): Short = 0

override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.facebook.react.viewmanagers.RNCWebViewManagerInterface;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
Expand Down Expand Up @@ -226,7 +227,7 @@ public void setMenuItems(RNCWebViewWrapper view, @Nullable ReadableArray items)
}

@Override
@ReactProp(name = "suppressMenuItems ")
@ReactProp(name = "suppressMenuItems")
public void setSuppressMenuItems(RNCWebViewWrapper view, @Nullable ReadableArray items) {}

@Override
Expand Down Expand Up @@ -331,6 +332,12 @@ public void setWebviewDebuggingEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setWebviewDebuggingEnabled(view, value);
}

@Override
@ReactProp(name = "paymentRequestEnabled")
public void setPaymentRequestEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setPaymentRequestEnabled(view, value);
}

/* iOS PROPS - no implemented here */
@Override
public void setAllowingReadAccessToURL(RNCWebViewWrapper view, @Nullable String value) {}
Expand Down Expand Up @@ -395,6 +402,9 @@ public void setPullToRefreshEnabled(RNCWebViewWrapper view, boolean value) {}
@Override
public void setRefreshControlLightMode(RNCWebViewWrapper view, boolean value) {}

@Override
public void setIndicatorStyle(RNCWebViewWrapper view, @Nullable String value) {}

@Override
public void setScrollEnabled(RNCWebViewWrapper view, boolean value) {}

Expand Down Expand Up @@ -515,6 +525,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
export.put(SubResourceErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingSubResourceError"));
export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
// !Default events but adding them here explicitly for clarity

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
Expand Down Expand Up @@ -273,6 +274,11 @@ public void setUserAgent(RNCWebViewWrapper view, @Nullable String value) {
mRNCWebViewManagerImpl.setUserAgent(view, value);
}

@ReactProp(name = "paymentRequestEnabled")
public void setPaymentRequestEnabled(RNCWebViewWrapper view, boolean value) {
mRNCWebViewManagerImpl.setPaymentRequestEnabled(view, value);
}

@Override
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, RNCWebViewWrapper viewWrapper) {
// Do not register default touch emitter and let WebView implementation handle touches
Expand All @@ -289,6 +295,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
export.put(SubResourceErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingSubResourceError"));
export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
// !Default events but adding them here explicitly for clarity

Expand Down
2 changes: 1 addition & 1 deletion apple/RNCWebView.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN

namespace facebook {
namespace react {
bool operator==(const RNCWebViewMenuItemsStruct& a, const RNCWebViewMenuItemsStruct& b)
inline bool operator==(const RNCWebViewMenuItemsStruct& a, const RNCWebViewMenuItemsStruct& b)
{
return b.key == a.key && b.label == a.label;
}
Expand Down
27 changes: 18 additions & 9 deletions apple/RNCWebView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ - (instancetype)initWithFrame:(CGRect)frame
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RNCWebViewProps>();
_props = defaultProps;

_view = [[RNCWebViewImpl alloc] init];

_view.onShouldStartLoadWithRequest = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto webViewEventEmitter = std::static_pointer_cast<RNCWebViewEventEmitter const>(_eventEmitter);
Expand Down Expand Up @@ -191,7 +191,7 @@ - (instancetype)initWithFrame:(CGRect)frame
.selectedText = std::string([[dictionary valueForKey:@"selectedText"] UTF8String]),
.key = std::string([[dictionary valueForKey:@"key"] UTF8String]),
.label = std::string([[dictionary valueForKey:@"label"] UTF8String])

};
webViewEventEmitter->onCustomMenuSelection(data);
}
Expand Down Expand Up @@ -312,7 +312,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
REMAP_WEBVIEW_PROP(showsHorizontalScrollIndicator)
REMAP_WEBVIEW_PROP(showsVerticalScrollIndicator)
REMAP_WEBVIEW_PROP(keyboardDisplayRequiresUserAction)

#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
REMAP_WEBVIEW_PROP(automaticallyAdjustContentInsets)
#endif
Expand Down Expand Up @@ -398,7 +398,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
for (const auto &menuItem: newViewProps.suppressMenuItems) {
[suppressMenuItems addObject: RCTNSStringFromString(menuItem)];
}

[_view setSuppressMenuItems:suppressMenuItems];
}
if (oldViewProps.hasOnFileDownload != newViewProps.hasOnFileDownload) {
Expand All @@ -410,10 +410,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
.downloadUrl = std::string([[dictionary valueForKey:@"downloadUrl"] UTF8String])
};
webViewEventEmitter->onFileDownload(data);
}
}
};
} else {
_view.onFileDownload = nil;
_view.onFileDownload = nil;
}
}
if (oldViewProps.hasOnOpenWindowEvent != newViewProps.hasOnOpenWindowEvent) {
Expand Down Expand Up @@ -459,7 +459,16 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
}
}
#endif

if (oldViewProps.indicatorStyle != newViewProps.indicatorStyle) {
if (newViewProps.indicatorStyle == RNCWebViewIndicatorStyle::Black) {
[_view setIndicatorStyle:@"black"];
} else if (newViewProps.indicatorStyle == RNCWebViewIndicatorStyle::White) {
[_view setIndicatorStyle:@"white"];
} else {
[_view setIndicatorStyle:@"default"];
}
}

NSMutableDictionary* source = [[NSMutableDictionary alloc] init];
if (!newViewProps.newSource.uri.empty()) {
[source setValue:RCTNSStringFromString(newViewProps.newSource.uri) forKey:@"uri"];
Expand All @@ -484,7 +493,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
[source setValue:RCTNSStringFromString(newViewProps.newSource.method) forKey:@"method"];
}
[_view setSource:source];

[super updateProps:props oldProps:oldProps];
}

Expand Down
1 change: 1 addition & 0 deletions apple/RNCWebViewImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
@property (nonatomic, assign) BOOL allowsLinkPreview;
@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;
@property (nonatomic, assign) BOOL showsVerticalScrollIndicator;
@property (nonatomic, copy) NSString * _Nullable indicatorStyle;
@property (nonatomic, assign) BOOL directionalLockEnabled;
@property (nonatomic, assign) BOOL ignoreSilentHardwareSwitch;
@property (nonatomic, copy) NSString * _Nullable allowingReadAccessToURL;
Expand Down
Loading
Loading