mirror of
https://github.com/status-im/react-native-webview.git
synced 2026-08-31 11:41:08 +00:00
Make it so that if you change the userAgent prop, this change is propagated to the native webview on iOS/macOS. Android already has this functionality. Sometimes it is useful for WebView apps to be able to change their user agent dynamically to communicate custom things to the web app they are hosting. Since we can't add custom headers to every single request made by the webview, the user agent can be used to send things like feature flags to the web app, so it can have the appropriate behavior depending on what state the mobile app is in.
256 lines
9.4 KiB
Objective-C
256 lines
9.4 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import "RNCWebViewManager.h"
|
|
|
|
#import <React/RCTUIManager.h>
|
|
#import <React/RCTDefines.h>
|
|
#import "RNCWebView.h"
|
|
|
|
@interface RNCWebViewManager () <RNCWebViewDelegate>
|
|
@end
|
|
|
|
@implementation RCTConvert (WKWebView)
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
|
|
RCT_ENUM_CONVERTER(WKContentMode, (@{
|
|
@"recommended": @(WKContentModeRecommended),
|
|
@"mobile": @(WKContentModeMobile),
|
|
@"desktop": @(WKContentModeDesktop),
|
|
}), WKContentModeRecommended, integerValue)
|
|
#endif
|
|
@end
|
|
|
|
@implementation RNCWebViewManager
|
|
{
|
|
NSConditionLock *_shouldStartLoadLock;
|
|
BOOL _shouldStartLoad;
|
|
}
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
#if !TARGET_OS_OSX
|
|
- (UIView *)view
|
|
#else
|
|
- (RCTUIView *)view
|
|
#endif // !TARGET_OS_OSX
|
|
{
|
|
RNCWebView *webView = [RNCWebView new];
|
|
webView.delegate = self;
|
|
return webView;
|
|
}
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
|
|
RCT_EXPORT_VIEW_PROPERTY(onFileDownload, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingProgress, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onHttpError, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
|
|
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoaded, NSString)
|
|
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptForMainFrameOnly, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoadedForMainFrameOnly, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(javaScriptCanOpenWindowsAutomatically, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(allowUniversalAccessFromFileURLs, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL)
|
|
#if WEBKIT_IOS_10_APIS_AVAILABLE
|
|
RCT_EXPORT_VIEW_PROPERTY(dataDetectorTypes, WKDataDetectorTypes)
|
|
#endif
|
|
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
|
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(autoManageStatusBarEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(allowsBackForwardNavigationGestures, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(incognito, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(applicationNameForUserAgent, NSString)
|
|
RCT_EXPORT_VIEW_PROPERTY(cacheEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(allowingReadAccessToURL, NSString)
|
|
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
|
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
|
|
#endif
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
|
|
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustsScrollIndicatorInsets, BOOL)
|
|
#endif
|
|
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */
|
|
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.
|
|
*/
|
|
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(enableApplePay, BOOL)
|
|
|
|
RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
|
|
RNCWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RNCWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
|
|
} else {
|
|
[view postMessage:message];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(pullToRefreshEnabled, BOOL, RNCWebView) {
|
|
view.pullToRefreshEnabled = json == nil ? false : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RNCWebView) {
|
|
view.bounces = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(useSharedProcessPool, BOOL, RNCWebView) {
|
|
view.useSharedProcessPool = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(userAgent, NSString, RNCWebView) {
|
|
view.userAgent = [RCTConvert NSString: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWebView) {
|
|
view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(sharedCookiesEnabled, BOOL, RNCWebView) {
|
|
view.sharedCookiesEnabled = json == nil ? false : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
#if !TARGET_OS_OSX
|
|
RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWebView) {
|
|
view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json];
|
|
}
|
|
#endif // !TARGET_OS_OSX
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(directionalLockEnabled, BOOL, RNCWebView) {
|
|
view.directionalLockEnabled = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL, RNCWebView) {
|
|
view.showsHorizontalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL, RNCWebView) {
|
|
view.showsVerticalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_CUSTOM_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL, RNCWebView) {
|
|
view.keyboardDisplayRequiresUserAction = json == nil ? true : [RCTConvert BOOL: json];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
|
|
RNCWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RNCWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
|
|
} else {
|
|
[view injectJavaScript:script];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
|
|
RNCWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RNCWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
|
|
} else {
|
|
[view goBack];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
|
|
RNCWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RNCWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
|
|
} else {
|
|
[view goForward];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
|
|
RNCWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RNCWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
|
|
} else {
|
|
[view reload];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWebView *> *viewRegistry) {
|
|
RNCWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RNCWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
|
|
} else {
|
|
[view stopLoading];
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Exported synchronous methods
|
|
|
|
- (BOOL) webView:(RNCWebView *)webView
|
|
shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
|
|
withCallback:(RCTDirectEventBlock)callback
|
|
{
|
|
_shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()];
|
|
_shouldStartLoad = YES;
|
|
request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition);
|
|
callback(request);
|
|
|
|
// Block the main thread for a maximum of 250ms until the JS thread returns
|
|
if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) {
|
|
BOOL returnValue = _shouldStartLoad;
|
|
[_shouldStartLoadLock unlock];
|
|
_shouldStartLoadLock = nil;
|
|
return returnValue;
|
|
} else {
|
|
RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES");
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier)
|
|
{
|
|
if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) {
|
|
_shouldStartLoad = result;
|
|
[_shouldStartLoadLock unlockWithCondition:0];
|
|
} else {
|
|
RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: "
|
|
"got %lld, expected %lld", (long long)lockIdentifier, (long long)_shouldStartLoadLock.condition);
|
|
}
|
|
}
|
|
|
|
@end
|