Implement message passing!
Summary: @public This diff implements message passing between the `WKWebView` and React Native. As with `<WebView/>`, we can only send/receive strings. **Usage:** 1. Set `messagingEnabled` to `true`. 1. To send data from the web view to React Native, call `postMessage(data)` within the web view. This forces React Native to execute the `onMessage` prop on the `WKWebView` component. `onMessage` will be called with an event `e`, where `e.nativeEvent.data` will be the data you passed into `postMessage`. 1. To send data from React Native to the web view, call `UIManager.dispatchViewManagerCommand` to dispatch the `UIManager.RCTWKWebView.Commands.postMessage` command. Look at [[ https://fburl.com/u1wusf2f | this part of the existing `<WebView/>` ]] component for more details. After you make the call, React Native will dispatch a `'message'` event to the `document` object within the webview. You can listen to the event by doing `document.addEventListener('message', callback)`. Let the event dispatched be `e`. Then, `e.data` is the data you sent over from React Native. [[ P58627181 | This Playground.js ]] illustrates the usage. Reviewed By: shergin Differential Revision: D6304850 fbshipit-source-id: 29075ef753296e9fb5a9cddeb1ad0f4ff7e28650
This commit is contained in:
parent
3703927e7e
commit
7a6dd9807c
|
@ -18,6 +18,9 @@
|
|||
|
||||
@property (nonatomic, weak) id<RCTWKWebViewDelegate> delegate;
|
||||
@property (nonatomic, copy) NSDictionary *source;
|
||||
@property (nonatomic, assign) BOOL messagingEnabled;
|
||||
@property (nonatomic, copy) NSString *injectedJavaScript;
|
||||
|
||||
- (void)postMessage:(NSString *)message;
|
||||
|
||||
@end
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
#import "RCTWKWebView.h"
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "RCTAutoInsetsProtocol.h"
|
||||
|
||||
@interface RCTWKWebView () <WKUIDelegate, WKNavigationDelegate>
|
||||
static NSString *const MessageHanderName = @"ReactNative";
|
||||
|
||||
@interface RCTWKWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler>
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
|
||||
@property (nonatomic, copy) WKWebView *webView;
|
||||
@end
|
||||
|
||||
|
@ -26,7 +26,11 @@
|
|||
{
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
super.backgroundColor = [UIColor clearColor];
|
||||
_webView = [[WKWebView alloc] initWithFrame:self.bounds];
|
||||
WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
|
||||
wkWebViewConfig.userContentController = [WKUserContentController new];
|
||||
[wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName];
|
||||
|
||||
_webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
|
||||
_webView.UIDelegate = self;
|
||||
_webView.navigationDelegate = self;
|
||||
[self addSubview:_webView];
|
||||
|
@ -34,6 +38,20 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called whenever JavaScript running within the web view calls:
|
||||
* - window.webkit.messageHandlers.[MessageHanderName].postMessage
|
||||
*/
|
||||
- (void)userContentController:(WKUserContentController *)userContentController
|
||||
didReceiveScriptMessage:(WKScriptMessage *)message
|
||||
{
|
||||
if (_onMessage != nil) {
|
||||
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
|
||||
[event addEntriesFromDictionary: @{@"data": message.body}];
|
||||
_onMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSource:(NSDictionary *)source
|
||||
{
|
||||
if (![_source isEqualToDictionary:source]) {
|
||||
|
@ -67,9 +85,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)postMessage:(NSString *)message
|
||||
{
|
||||
NSDictionary *eventInitDict = @{@"data": message};
|
||||
NSString *source = [NSString
|
||||
stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
|
||||
RCTJSONStringify(eventInitDict, NULL)
|
||||
];
|
||||
[self evaluateJS: source thenCall: nil];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
// Ensure webview takes the position and dimensions of RCTWKWebView
|
||||
_webView.frame = self.bounds;
|
||||
}
|
||||
|
||||
|
@ -161,7 +191,7 @@
|
|||
thenCall: (void (^)(NSString*)) callback
|
||||
{
|
||||
[self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
|
||||
if (error == nil) {
|
||||
if (error == nil && callback != nil) {
|
||||
callback([NSString stringWithFormat:@"%@", result]);
|
||||
}
|
||||
}];
|
||||
|
@ -175,6 +205,31 @@
|
|||
- (void) webView:(WKWebView *)webView
|
||||
didFinishNavigation:(WKNavigation *)navigation
|
||||
{
|
||||
if (_messagingEnabled) {
|
||||
#if RCT_DEV
|
||||
|
||||
// Implementation inspired by Lodash.isNative.
|
||||
NSString *isPostMessageNative = @"String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))";
|
||||
[self evaluateJS: isPostMessageNative thenCall: ^(NSString *result) {
|
||||
if (! [result isEqualToString:@"true"]) {
|
||||
RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
|
||||
}
|
||||
}];
|
||||
#endif
|
||||
|
||||
NSString *source = [NSString stringWithFormat:
|
||||
@"(function() {"
|
||||
"window.originalPostMessage = window.postMessage;"
|
||||
|
||||
"window.postMessage = function(data) {"
|
||||
"window.webkit.messageHandlers.%@.postMessage(String(data));"
|
||||
"};"
|
||||
"})();",
|
||||
MessageHanderName
|
||||
];
|
||||
[self evaluateJS: source thenCall: nil];
|
||||
}
|
||||
|
||||
if (_injectedJavaScript) {
|
||||
[self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
|
||||
NSMutableDictionary *event = [self baseEvent];
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface RCTWKWebViewManager : RCTViewManager
|
||||
@end
|
|
@ -1,8 +1,7 @@
|
|||
#import "RCTViewManager.h"
|
||||
#import "RCTWKWebView.h"
|
||||
#import "RCTWKWebViewManager.h"
|
||||
|
||||
@interface RCTWKWebViewManager : RCTViewManager
|
||||
@end
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTWKWebView.h"
|
||||
|
||||
@implementation RCTWKWebViewManager
|
||||
|
||||
|
@ -19,4 +18,22 @@ RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
|
|||
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
|
||||
|
||||
/**
|
||||
* Expose methods to enable messaging the webview.
|
||||
*/
|
||||
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
|
||||
RCTWKWebView *view = viewRegistry[reactTag];
|
||||
if (![view isKindOfClass:[RCTWKWebView class]]) {
|
||||
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
||||
} else {
|
||||
[view postMessage:message];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in New Issue