From 00992b3c3794d7ae7ae63d1facee200bb4e43c23 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Wed, 13 Jul 2016 11:36:56 -0700 Subject: [PATCH 1/5] Add evaluateJavaScript to iOS WebView --- Libraries/Components/WebView/WebView.ios.js | 33 +++++++++++++++++++++ React/Views/RCTWebView.h | 1 + React/Views/RCTWebView.m | 5 ++++ React/Views/RCTWebViewManager.m | 12 ++++++++ 4 files changed, 51 insertions(+) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 018f913d1c4..fdf37b877e6 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -461,6 +461,39 @@ var WebView = React.createClass({ ); }, + /** + * Evaluate JavaScript on the current page. + */ + evaluateJavaScript: function( + script: string, + callback?: ?(error: ?Error, result: ?string) => void, + ): Promise { + return new Promise((resolve, reject) => { + var escaped = script.replace(/\\/g, '\\\\') + .replace(/"/g, '\\"'); + var wrapped = 'JSON.stringify(eval("' + escaped + '"))'; + + RCTWebViewManager.evaluateJavaScript(this.getWebViewHandle(), wrapped, function(error, result) { + if (!error) { + if (result === '') { + error = new Error("Error evaluating script.") + result = null; + } else { + result = JSON.parse(result); + } + } + + callback && callback(error, result); + + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + }, + /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h index f8bd2608cc0..0b3bbb71ccb 100644 --- a/React/Views/RCTWebView.h +++ b/React/Views/RCTWebView.h @@ -41,5 +41,6 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request - (void)goBack; - (void)reload; - (void)stopLoading; +- (NSString *)evaluateJavaScript:(NSString *)script; @end diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index 76c3f096003..38a0fc41d07 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -82,6 +82,11 @@ - (void)stopLoading [_webView stopLoading]; } +- (NSString *)evaluateJavaScript: (NSString*) script +{ + return [_webView stringByEvaluatingJavaScriptFromString: script]; +} + - (void)setSource:(NSDictionary *)source { if (![_source isEqualToDictionary:source]) { diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index af5f5b03b8a..8f16a89db8f 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -97,6 +97,18 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(evaluateJavaScript:(nonnull NSNumber *)reactTag script: (nonnull NSString *)script callback:(RCTResponseSenderBlock)callback) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTWebView *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebView class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + callback(@[[NSNull null], [view evaluateJavaScript: script]]); + } + }]; +} + #pragma mark - Exported synchronous methods - (BOOL)webView:(__unused RCTWebView *)webView From 4e0e9e81c4d97c222d1a97465de9ad4267d62025 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Thu, 14 Jul 2016 20:31:07 -0700 Subject: [PATCH 2/5] Review comment: Fix escaping --- Libraries/Components/WebView/WebView.ios.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index fdf37b877e6..99e03b7611f 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -469,9 +469,8 @@ var WebView = React.createClass({ callback?: ?(error: ?Error, result: ?string) => void, ): Promise { return new Promise((resolve, reject) => { - var escaped = script.replace(/\\/g, '\\\\') - .replace(/"/g, '\\"'); - var wrapped = 'JSON.stringify(eval("' + escaped + '"))'; + var escaped = JSON.stringify(script).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); + var wrapped = 'JSON.stringify(eval(' + escaped + '))'; RCTWebViewManager.evaluateJavaScript(this.getWebViewHandle(), wrapped, function(error, result) { if (!error) { From 9d380310cc6fe72718ffc8aa52ef80d581ab301a Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Fri, 15 Jul 2016 13:42:24 -0700 Subject: [PATCH 3/5] Use RCTPromise instead of RCTResponse --- Libraries/Components/WebView/WebView.ios.js | 39 ++++++++------------- React/Views/RCTWebViewManager.m | 10 ++++-- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 99e03b7611f..b9de058824f 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -464,33 +464,24 @@ var WebView = React.createClass({ /** * Evaluate JavaScript on the current page. */ - evaluateJavaScript: function( + evaluateJavaScript: async function( script: string, callback?: ?(error: ?Error, result: ?string) => void, ): Promise { - return new Promise((resolve, reject) => { - var escaped = JSON.stringify(script).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); - var wrapped = 'JSON.stringify(eval(' + escaped + '))'; - - RCTWebViewManager.evaluateJavaScript(this.getWebViewHandle(), wrapped, function(error, result) { - if (!error) { - if (result === '') { - error = new Error("Error evaluating script.") - result = null; - } else { - result = JSON.parse(result); - } - } - - callback && callback(error, result); - - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); + var escaped = JSON.stringify(script).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); + var wrapped = 'JSON.stringify(eval(' + escaped + '))'; + var result = null; + + try { + var resultString = await RCTWebViewManager.evaluateJavaScript(this.getWebViewHandle(), wrapped); + result = JSON.parse(resultString); + } catch (e) { + callback && callback(e, null); + throw e; + } + + callback && callback(null, result); + return result; }, /** diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 8f16a89db8f..f2a46ae5da8 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -13,6 +13,7 @@ #import "RCTUIManager.h" #import "RCTWebView.h" #import "UIView+React.h" +#import "RCTUtils.h" @interface RCTWebViewManager () @@ -97,14 +98,19 @@ - (UIView *)view }]; } -RCT_EXPORT_METHOD(evaluateJavaScript:(nonnull NSNumber *)reactTag script: (nonnull NSString *)script callback:(RCTResponseSenderBlock)callback) +RCT_REMAP_METHOD(evaluateJavaScript, evaluateJavaScript:(nonnull NSNumber *)reactTag script: (nonnull NSString *)script resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); } else { - callback(@[[NSNull null], [view evaluateJavaScript: script]]); + NSString *result = [view evaluateJavaScript: script]; + if (result && [result length] > 0) { + resolve(result); + } else { + reject(RCTErrorUnspecified, @"Error evaluating script.", nil); + } } }]; } From 89e6d1442308a5018adb0ef9a1d4686d48d93013 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Fri, 15 Jul 2016 14:55:51 -0700 Subject: [PATCH 4/5] Removed callback support --- Libraries/Components/WebView/WebView.ios.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index b9de058824f..109035aec5b 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -464,24 +464,11 @@ var WebView = React.createClass({ /** * Evaluate JavaScript on the current page. */ - evaluateJavaScript: async function( - script: string, - callback?: ?(error: ?Error, result: ?string) => void, - ): Promise { + evaluateJavaScript: async function(script: string): Promise { var escaped = JSON.stringify(script).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); var wrapped = 'JSON.stringify(eval(' + escaped + '))'; - var result = null; - - try { - var resultString = await RCTWebViewManager.evaluateJavaScript(this.getWebViewHandle(), wrapped); - result = JSON.parse(resultString); - } catch (e) { - callback && callback(e, null); - throw e; - } - - callback && callback(null, result); - return result; + var resultString = await RCTWebViewManager.evaluateJavaScript(this.getWebViewHandle(), wrapped); + return JSON.parse(resultString); }, /** From 74971b37c8e28a05fd67f5121d7c99ecd453a085 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Mon, 18 Jul 2016 10:33:05 -0700 Subject: [PATCH 5/5] Fix whitespace and formatting --- React/Views/RCTWebView.m | 2 +- React/Views/RCTWebViewManager.m | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index 38a0fc41d07..e103417ca1e 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -82,7 +82,7 @@ - (void)stopLoading [_webView stopLoading]; } -- (NSString *)evaluateJavaScript: (NSString*) script +- (NSString *)evaluateJavaScript:(NSString*)script { return [_webView stringByEvaluatingJavaScriptFromString: script]; } diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index f2a46ae5da8..ef4cf9a7bc4 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -98,19 +98,20 @@ - (UIView *)view }]; } -RCT_REMAP_METHOD(evaluateJavaScript, evaluateJavaScript:(nonnull NSNumber *)reactTag script: (nonnull NSString *)script resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +RCT_REMAP_METHOD(evaluateJavaScript, evaluateJavaScript:(nonnull NSNumber *)reactTag script:(nonnull NSString *)script resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + return; + } + + NSString *result = [view evaluateJavaScript: script]; + if (result && [result length] > 0) { + resolve(result); } else { - NSString *result = [view evaluateJavaScript: script]; - if (result && [result length] > 0) { - resolve(result); - } else { - reject(RCTErrorUnspecified, @"Error evaluating script.", nil); - } + reject(RCTErrorUnspecified, @"Error evaluating script.", nil); } }]; }