diff --git a/README.md b/README.md index d38985197..8911bfc00 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ For more, read the [API Reference](./docs/Reference.md) and [Guide](./docs/Guide ## Common issues - If you're getting `Invariant Violation: Native component for "RNCWebView does not exist"` it likely means you forgot to run `react-native link` or there was some error with the linking process +- If you encounter a build error during the task `:app:mergeDexRelease`, you need to enable multidex support in `android/app/build.gradle` as discussed in [this issue](https://github.com/react-native-webview/react-native-webview/issues/1344#issuecomment-650544648) ## Contributing diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index b62317313..de71ad79a 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -736,8 +736,25 @@ public void onShowCustomView(View view, CustomViewCallback callback) { } mVideoView.setBackgroundColor(Color.BLACK); - getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS); - mWebView.setVisibility(View.GONE); + + // since RN's Modals interfere with the View hierarchy + // we will decide which View to Hide if the hierarchy + // does not match (i.e., the webview is within a Modal) + // NOTE: We could use mWebView.getRootView() instead of getRootView() + // but that breaks the Modal's styles and layout, so we need this to render + // in the main View hierarchy regardless. + ViewGroup rootView = getRootView(); + rootView.addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS); + + // Different root views, we are in a Modal + if(rootView.getRootView() != mWebView.getRootView()){ + mWebView.getRootView().setVisibility(View.GONE); + } + + // Same view hierarchy (no Modal), just hide the webview then + else{ + mWebView.setVisibility(View.GONE); + } mReactContext.addLifecycleEventListener(this); } @@ -748,14 +765,17 @@ public void onHideCustomView() { return; } - mVideoView.setVisibility(View.GONE); - getRootView().removeView(mVideoView); - mCustomViewCallback.onCustomViewHidden(); + // same logic as above + ViewGroup rootView = getRootView(); - mVideoView = null; - mCustomViewCallback = null; + if(rootView.getRootView() != mWebView.getRootView()){ + mWebView.getRootView().setVisibility(View.VISIBLE); + } - mWebView.setVisibility(View.VISIBLE); + // Same view hierarchy (no Modal) + else{ + mWebView.setVisibility(View.VISIBLE); + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -770,6 +790,15 @@ public void onHideCustomView() { mReactContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } + + rootView.removeView(mVideoView); + mCustomViewCallback.onCustomViewHidden(); + + mVideoView = null; + mCustomViewCallback = null; + + mReactContext.getCurrentActivity().setRequestedOrientation(initialRequestedOrientation); + mReactContext.removeLifecycleEventListener(this); } }; @@ -1122,6 +1151,8 @@ public void onPermissionRequest(final PermissionRequest request) { permissions.add(Manifest.permission.RECORD_AUDIO); } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) { permissions.add(Manifest.permission.CAMERA); + } else if(requestedResources[i].equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) { + permissions.add(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID); } // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID. } @@ -1134,6 +1165,8 @@ public void onPermissionRequest(final PermissionRequest request) { grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE); } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) { grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE); + } else if (permissions.get(i).equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) { + grantedPermissions.add(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID); } } diff --git a/apple/RNCWebView.h b/apple/RNCWebView.h index dd93ca216..e2f2d8818 100644 --- a/apple/RNCWebView.h +++ b/apple/RNCWebView.h @@ -20,8 +20,11 @@ @end @interface RNCWeakScriptMessageDelegate : NSObject -@property (nonatomic, weak) id scriptDelegate; -- (instancetype)initWithDelegate:(id)scriptDelegate; + +@property (nonatomic, weak, nullable) id scriptDelegate; + +- (nullable instancetype)initWithDelegate:(id _Nullable)scriptDelegate; + @end @interface RNCWebView : RCTView @@ -66,13 +69,17 @@ @property (nonatomic, copy) NSString * _Nullable allowingReadAccessToURL; @property (nonatomic, assign) BOOL pullToRefreshEnabled; #if !TARGET_OS_OSX -@property (nonatomic, weak) UIRefreshControl * refreshControl; +@property (nonatomic, weak) UIRefreshControl * _Nullable refreshControl; #endif #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */ @property (nonatomic, assign) WKContentMode contentMode; #endif +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */ +@property (nonatomic, assign) BOOL limitsNavigationsToAppBoundDomains; +#endif + + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential; + (void)setCustomCertificatesForHost:(nullable NSDictionary *)certificates; - (void)postMessage:(NSString *_Nullable)message; @@ -85,7 +92,7 @@ - (void)stopLoading; #if !TARGET_OS_OSX - (void)addPullToRefreshControl; -- (void)pullToRefresh:(UIRefreshControl *)refreshControl; +- (void)pullToRefresh:(UIRefreshControl *_Nonnull)refreshControl; #endif @end diff --git a/apple/RNCWebView.m b/apple/RNCWebView.m index c2b77b91f..bdf35859f 100644 --- a/apple/RNCWebView.m +++ b/apple/RNCWebView.m @@ -245,6 +245,16 @@ - (WKWebViewConfiguration *)setUpWkWebViewConfig } #endif +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */ + if (@available(iOS 14.0, *)) { + if ([wkWebViewConfig respondsToSelector:@selector(limitsNavigationsToAppBoundDomains)]) { + if (_limitsNavigationsToAppBoundDomains) { + wkWebViewConfig.limitsNavigationsToAppBoundDomains = YES; + } + } + } +#endif + // Shim the HTML5 history API: [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] name:HistoryShimName]; @@ -331,6 +341,7 @@ - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigation - (void)removeFromSuperview { if (_webView) { + [_webView.configuration.userContentController removeScriptMessageHandlerForName:HistoryShimName]; [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName]; [_webView removeObserver:self forKeyPath:@"estimatedProgress"]; [_webView removeFromSuperview]; @@ -543,6 +554,16 @@ - (void)visitSource } [_webView loadHTMLString:html baseURL:baseURL]; return; + } + //Add cookie for subsequent resource requests sent by page itself, if cookie was set in headers on WebView + NSString *headerCookie = [RCTConvert NSString:_source[@"headers"][@"cookie"]]; + if(headerCookie) { + NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:headerCookie,@"Set-Cookie",nil]; + NSURL *urlString = [NSURL URLWithString:_source[@"uri"]]; + NSArray *httpCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:urlString]; + for (NSHTTPCookie *httpCookie in httpCookies) { + [_webView.configuration.websiteDataStore.httpCookieStore setCookie:httpCookie completionHandler:nil]; + } } NSURLRequest *request = [self requestForSource:_source]; @@ -1053,7 +1074,8 @@ - (void) webView:(WKWebView *)webView return; } - if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102 || [error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 101) { + if ([error.domain isEqualToString:@"WebKitErrorDomain"] && + (error.code == 102 || error.code == 101)) { // Error code 102 "Frame load interrupted" is raised by the WKWebView // when the URL is from an http redirect. This is a common pattern when // implementing OAuth with a WebView. diff --git a/apple/RNCWebViewManager.m b/apple/RNCWebViewManager.m index 206b5dbc7..553c19b56 100644 --- a/apple/RNCWebViewManager.m +++ b/apple/RNCWebViewManager.m @@ -90,6 +90,10 @@ - (RCTUIView *)view RCT_EXPORT_VIEW_PROPERTY(contentMode, WKContentMode) #endif +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */ +RCT_EXPORT_VIEW_PROPERTY(limitsNavigationsToAppBoundDomains, BOOL) +#endif + /** * Expose methods to enable messaging the webview. */ diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 8631a4843..3de306ad4 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -83,6 +83,8 @@ Add `#include "winrt/ReactNativeWebView.h"`. Add `PackageProviders().Append(winrt::ReactNativeWebView::ReactPackageProvider());` before `InitializeComponent();`. +Note if you want to enable scroll with Touch for the WebView component you must disable perspective for your app using [ReactRootView.IsPerspectiveEnabled](https://microsoft.github.io/react-native-windows/docs/ReactRootView#isperspectiveenabled). + ## 3. Import the webview into your component ```js diff --git a/docs/Reference.md b/docs/Reference.md index 037293e75..7fd4ebaa8 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -73,6 +73,7 @@ This document lays out the current public properties and methods for the React N - [`pullToRefreshEnabled`](Reference.md#pullToRefreshEnabled) - [`ignoreSilentHardwareSwitch`](Reference.md#ignoreSilentHardwareSwitch) - [`onFileDownload`](Reference.md#onFileDownload) +- [`limitsNavigationsToAppBoundDomains`](Reference.md#limitsNavigationsToAppBoundDomains) - [`autoManageStatusBarEnabled`](Reference.md#autoManageStatusBarEnabled) - [`setSupportMultipleWindows`](Reference.md#setSupportMultipleWindows) @@ -88,7 +89,7 @@ This document lays out the current public properties and methods for the React N - [`clearCache`](Reference.md#clearCache) - [`clearHistory`](Reference.md#clearHistory) - [`requestFocus`](Reference.md#requestFocus) -- [`postMessage`](Reference.md#postMessage) +- [`postMessage`](Reference.md#postmessagestr) --- @@ -550,7 +551,7 @@ The `navState` object includes these properties: canGoBack canGoForward loading -navigationType +navigationType (iOS only) target title url @@ -729,8 +730,8 @@ canGoBack canGoForward lockIdentifier mainDocumentURL (iOS only) -navigationType -isTopFrame +navigationType (iOS only) +isTopFrame (iOS only) ``` --- @@ -1314,6 +1315,25 @@ Example: --- +### `limitsNavigationsToAppBoundDomains`[⬆](#props-index) + +If true indicates to WebKit that a WKWebView will only navigate to app-bound domains. Only applicable for iOS 14 or greater. + +Once set, any attempt to navigate away from an app-bound domain will fail with the error “App-bound domain failure.” +Applications can specify up to 10 “app-bound” domains using a new Info.plist key `WKAppBoundDomains`. For more information see [App-Bound Domains](https://webkit.org/blog/10882/app-bound-domains/). + +| Type | Required | Platform | +| ------- | -------- | -------- | +| boolean | No | iOS | + +Example: + +```jsx + +``` + +--- + ### `autoManageStatusBarEnabled` If set to `true`, the status bar will be automatically hidden/shown by WebView, specifically when full screen video is being watched. If `false`, WebView will not manage the status bar at all. The default value is `true`. diff --git a/package.json b/package.json index b25e2da13..6f48e5b30 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "Thibault Malbranche " ], "license": "MIT", - "version": "11.2.1", + "version": "11.3.1", "homepage": "https://github.com/react-native-webview/react-native-webview#readme", "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start", diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 580a6480f..60c41e691 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -332,6 +332,7 @@ export interface IOSNativeWebViewProps extends CommonNativeWebViewProps { injectedJavaScriptForMainFrameOnly?: boolean; injectedJavaScriptBeforeContentLoadedForMainFrameOnly?: boolean; onFileDownload?: (event: FileDownloadEvent) => void; + limitsNavigationsToAppBoundDomains?: boolean; } export interface MacOSNativeWebViewProps extends CommonNativeWebViewProps { @@ -614,6 +615,17 @@ export interface IOSWebViewProps extends WebViewSharedProps { * If not provided, the default is to let the webview try to render the file. */ onFileDownload?: (event: FileDownloadEvent) => void; + + /** + * A Boolean value which, when set to `true`, indicates to WebKit that a WKWebView + * will only navigate to app-bound domains. Once set, any attempt to navigate away + * from an app-bound domain will fail with the error “App-bound domain failure.” + * + * Applications can specify up to 10 “app-bound” domains using a new + * Info.plist key `WKAppBoundDomains`. + * @platform ios + */ + limitsNavigationsToAppBoundDomains?: boolean; } export interface MacOSWebViewProps extends WebViewSharedProps { diff --git a/windows/ReactNativeWebView.sln b/windows/ReactNativeWebView.sln index b0f11da39..4af32309f 100644 --- a/windows/ReactNativeWebView.sln +++ b/windows/ReactNativeWebView.sln @@ -34,7 +34,7 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9 - ..\node_modules\react-native-windows\ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603144}*SharedItemsImports = 4 + ..\node_modules\react-native-windows\ReactWindowsCore\ReactWindowsCore-Items.vcxitems*{11c084a3-a57c-4296-a679-cac17b603144}*SharedItemsImports = 4 ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4 ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4 ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4 @@ -44,7 +44,6 @@ Global ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 ..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 - ..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 ..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 ..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 ..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4 diff --git a/windows/ReactNativeWebView/ReactNativeWebView.vcxproj b/windows/ReactNativeWebView/ReactNativeWebView.vcxproj index 0f0ad05ad..46a756dcc 100644 --- a/windows/ReactNativeWebView/ReactNativeWebView.vcxproj +++ b/windows/ReactNativeWebView/ReactNativeWebView.vcxproj @@ -1,6 +1,6 @@ - + true true @@ -89,6 +89,7 @@ 28204 _WINRT_DLL;%(PreprocessorDefinitions) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + stdcpp17 Console @@ -153,7 +154,7 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. @@ -161,4 +162,4 @@ - + \ No newline at end of file diff --git a/windows/ReactNativeWebView/ReactWebView.cpp b/windows/ReactNativeWebView/ReactWebView.cpp index 6127fbe7d..045cec4ac 100644 --- a/windows/ReactNativeWebView/ReactWebView.cpp +++ b/windows/ReactNativeWebView/ReactWebView.cpp @@ -5,8 +5,8 @@ #include "JSValueXaml.h" #include "ReactWebView.h" #include "ReactWebView.g.cpp" - - +#include +#include namespace winrt { using namespace Microsoft::ReactNative; @@ -63,11 +63,22 @@ namespace winrt::ReactNativeWebView::implementation { }); } + bool Is17763OrHigher() { + static std::optional hasUniversalAPIContract_v7; + + if (!hasUniversalAPIContract_v7.has_value()) { + hasUniversalAPIContract_v7 = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7); + } + return hasUniversalAPIContract_v7.value(); + } + void ReactWebView::WriteWebViewNavigationEventArg(winrt::WebView const& sender, winrt::IJSValueWriter const& eventDataWriter) { auto tag = this->GetValue(winrt::FrameworkElement::TagProperty()).as().GetInt64(); WriteProperty(eventDataWriter, L"canGoBack", sender.CanGoBack()); WriteProperty(eventDataWriter, L"canGoForward", sender.CanGoForward()); - WriteProperty(eventDataWriter, L"loading", !sender.IsLoaded()); + if (Is17763OrHigher()) { + WriteProperty(eventDataWriter, L"loading", !sender.IsLoaded()); + } WriteProperty(eventDataWriter, L"target", tag); WriteProperty(eventDataWriter, L"title", sender.DocumentTitle()); if (auto uri = sender.Source()) { diff --git a/windows/WebViewBridgeComponent/WebViewBridgeComponent.vcxproj b/windows/WebViewBridgeComponent/WebViewBridgeComponent.vcxproj index a7e95096f..68a6b771a 100644 --- a/windows/WebViewBridgeComponent/WebViewBridgeComponent.vcxproj +++ b/windows/WebViewBridgeComponent/WebViewBridgeComponent.vcxproj @@ -95,6 +95,7 @@ _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + stdcpp17 Console @@ -148,6 +149,7 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.