mirror of
https://github.com/status-im/react-native.git
synced 2025-01-09 17:15:54 +00:00
da9a712a9e
Summary: Currently, < WebView > allows you to pass JS to execute within the view. This works great, but there currently is not a way to execute JS after the page is loaded. We needed this for our app. We noticed that the WebView had messaging support added (see #9762) . Initially, this seemed like more than enough functionality for our use case - just write a function that's injected on initial load that accepts a message with JS, and `eval()` it. However, this broke once we realized that Content Security Policy can block the use of eval on pages. The native methods iOS provide to inject JS allow you to inject JS without CSP interfering. So, we just wrapped the native methods on iOS (and later Android) and it worked for our use case. The method injectJavaScript was born. Now, after I wrote this code, I realized that #8798 exists and hadn't been merged because of a lack of tests. I commend what was done in #8798 as it sorely solves a problem (injecting JS after the initial load) and has more features than what I' Closes https://github.com/facebook/react-native/pull/11358 Differential Revision: D4390425 fbshipit-source-id: 02813127f8cf60fd84229cb26eeea7f8922d03b3
161 lines
5.7 KiB
Objective-C
161 lines
5.7 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTWebViewManager.h"
|
|
|
|
#import "RCTBridge.h"
|
|
#import "RCTUIManager.h"
|
|
#import "RCTWebView.h"
|
|
#import "UIView+React.h"
|
|
|
|
@interface RCTWebViewManager () <RCTWebViewDelegate>
|
|
|
|
@end
|
|
|
|
@implementation RCTWebViewManager
|
|
{
|
|
NSConditionLock *_shouldStartLoadLock;
|
|
BOOL _shouldStartLoad;
|
|
}
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (UIView *)view
|
|
{
|
|
RCTWebView *webView = [RCTWebView new];
|
|
webView.delegate = self;
|
|
return webView;
|
|
}
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
|
|
RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
|
|
RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
|
|
RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
|
|
RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
|
|
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
|
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
|
|
RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
|
|
RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL)
|
|
RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes)
|
|
|
|
RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
|
|
RCTWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RCTWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
|
} else {
|
|
[view goBack];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
id view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RCTWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
|
} else {
|
|
[view goForward];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
|
|
RCTWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RCTWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
|
} else {
|
|
[view reload];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
|
|
RCTWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RCTWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
|
} else {
|
|
[view stopLoading];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
|
|
RCTWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RCTWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
|
} else {
|
|
[view postMessage:message];
|
|
}
|
|
}];
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
|
|
RCTWebView *view = viewRegistry[reactTag];
|
|
if (![view isKindOfClass:[RCTWebView class]]) {
|
|
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
|
} else {
|
|
[view injectJavaScript:script];
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Exported synchronous methods
|
|
|
|
- (BOOL)webView:(__unused RCTWebView *)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 %zd, expected %zd", lockIdentifier, _shouldStartLoadLock.condition);
|
|
}
|
|
}
|
|
|
|
@end
|