Skip to content

Commit 94560ca

Browse files
RSNarakelset
authored andcommitted
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
1 parent f7f9d01 commit 94560ca

File tree

4 files changed

+91
-10
lines changed

4 files changed

+91
-10
lines changed

React/Views/RCTWKWebView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
@property (nonatomic, weak) id<RCTWKWebViewDelegate> delegate;
2020
@property (nonatomic, copy) NSDictionary *source;
21+
@property (nonatomic, assign) BOOL messagingEnabled;
2122
@property (nonatomic, copy) NSString *injectedJavaScript;
2223

24+
- (void)postMessage:(NSString *)message;
25+
2326
@end

React/Views/RCTWKWebView.m

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#import "RCTWKWebView.h"
2-
32
#import <WebKit/WebKit.h>
4-
53
#import <React/RCTConvert.h>
6-
74
#import "RCTAutoInsetsProtocol.h"
85

9-
@interface RCTWKWebView () <WKUIDelegate, WKNavigationDelegate>
6+
static NSString *const MessageHanderName = @"ReactNative";
7+
8+
@interface RCTWKWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler>
109
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
1110
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
1211
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
12+
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
1313
@property (nonatomic, copy) WKWebView *webView;
1414
@end
1515

@@ -26,14 +26,32 @@ - (instancetype)initWithFrame:(CGRect)frame
2626
{
2727
if ((self = [super initWithFrame:frame])) {
2828
super.backgroundColor = [UIColor clearColor];
29-
_webView = [[WKWebView alloc] initWithFrame:self.bounds];
29+
WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
30+
wkWebViewConfig.userContentController = [WKUserContentController new];
31+
[wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName];
32+
33+
_webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
3034
_webView.UIDelegate = self;
3135
_webView.navigationDelegate = self;
3236
[self addSubview:_webView];
3337
}
3438
return self;
3539
}
3640

41+
/**
42+
* This method is called whenever JavaScript running within the web view calls:
43+
* - window.webkit.messageHandlers.[MessageHanderName].postMessage
44+
*/
45+
- (void)userContentController:(WKUserContentController *)userContentController
46+
didReceiveScriptMessage:(WKScriptMessage *)message
47+
{
48+
if (_onMessage != nil) {
49+
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
50+
[event addEntriesFromDictionary: @{@"data": message.body}];
51+
_onMessage(event);
52+
}
53+
}
54+
3755
- (void)setSource:(NSDictionary *)source
3856
{
3957
if (![_source isEqualToDictionary:source]) {
@@ -67,9 +85,21 @@ - (void)setSource:(NSDictionary *)source
6785
}
6886
}
6987

88+
- (void)postMessage:(NSString *)message
89+
{
90+
NSDictionary *eventInitDict = @{@"data": message};
91+
NSString *source = [NSString
92+
stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
93+
RCTJSONStringify(eventInitDict, NULL)
94+
];
95+
[self evaluateJS: source thenCall: nil];
96+
}
97+
7098
- (void)layoutSubviews
7199
{
72100
[super layoutSubviews];
101+
102+
// Ensure webview takes the position and dimensions of RCTWKWebView
73103
_webView.frame = self.bounds;
74104
}
75105

@@ -161,7 +191,7 @@ - (void)evaluateJS:(NSString *)js
161191
thenCall: (void (^)(NSString*)) callback
162192
{
163193
[self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
164-
if (error == nil) {
194+
if (error == nil && callback != nil) {
165195
callback([NSString stringWithFormat:@"%@", result]);
166196
}
167197
}];
@@ -175,6 +205,31 @@ - (void)evaluateJS:(NSString *)js
175205
- (void) webView:(WKWebView *)webView
176206
didFinishNavigation:(WKNavigation *)navigation
177207
{
208+
if (_messagingEnabled) {
209+
#if RCT_DEV
210+
211+
// Implementation inspired by Lodash.isNative.
212+
NSString *isPostMessageNative = @"String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))";
213+
[self evaluateJS: isPostMessageNative thenCall: ^(NSString *result) {
214+
if (! [result isEqualToString:@"true"]) {
215+
RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
216+
}
217+
}];
218+
#endif
219+
220+
NSString *source = [NSString stringWithFormat:
221+
@"(function() {"
222+
"window.originalPostMessage = window.postMessage;"
223+
224+
"window.postMessage = function(data) {"
225+
"window.webkit.messageHandlers.%@.postMessage(String(data));"
226+
"};"
227+
"})();",
228+
MessageHanderName
229+
];
230+
[self evaluateJS: source thenCall: nil];
231+
}
232+
178233
if (_injectedJavaScript) {
179234
[self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
180235
NSMutableDictionary *event = [self baseEvent];

React/Views/RCTWKWebViewManager.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
#import <React/RCTViewManager.h>
4+
5+
@interface RCTWKWebViewManager : RCTViewManager
6+
@end

React/Views/RCTWKWebViewManager.m

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
#import "RCTViewManager.h"
2-
#import "RCTWKWebView.h"
1+
#import "RCTWKWebViewManager.h"
32

4-
@interface RCTWKWebViewManager : RCTViewManager
5-
@end
3+
#import "RCTUIManager.h"
4+
#import "RCTWKWebView.h"
65

76
@implementation RCTWKWebViewManager
87

@@ -19,4 +18,22 @@ - (UIView *)view
1918
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
2019
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
2120

21+
/**
22+
* Expose methods to enable messaging the webview.
23+
*/
24+
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
25+
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
26+
27+
RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
28+
{
29+
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWKWebView *> *viewRegistry) {
30+
RCTWKWebView *view = viewRegistry[reactTag];
31+
if (![view isKindOfClass:[RCTWKWebView class]]) {
32+
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
33+
} else {
34+
[view postMessage:message];
35+
}
36+
}];
37+
}
38+
2239
@end

0 commit comments

Comments
 (0)