Skip to content

Commit

Permalink
Implement message passing!
Browse files Browse the repository at this point in the history
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
  • Loading branch information
RSNara authored and facebook-github-bot committed Aug 16, 2018
1 parent 3703927 commit 7a6dd98
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 10 deletions.
3 changes: 3 additions & 0 deletions React/Views/RCTWKWebView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
67 changes: 61 additions & 6 deletions React/Views/RCTWKWebView.m
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -26,14 +26,32 @@ - (instancetype)initWithFrame:(CGRect)frame
{
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];
}
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]) {
Expand Down Expand Up @@ -67,9 +85,21 @@ - (void)setSource:(NSDictionary *)source
}
}

- (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;
}

Expand Down Expand Up @@ -161,7 +191,7 @@ - (void)evaluateJS:(NSString *)js
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]);
}
}];
Expand All @@ -175,6 +205,31 @@ - (void)evaluateJS:(NSString *)js
- (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];
Expand Down
6 changes: 6 additions & 0 deletions React/Views/RCTWKWebViewManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#import <React/RCTViewManager.h>

@interface RCTWKWebViewManager : RCTViewManager
@end
25 changes: 21 additions & 4 deletions React/Views/RCTWKWebViewManager.m
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -19,4 +18,22 @@ - (UIView *)view
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

0 comments on commit 7a6dd98

Please sign in to comment.