diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 15ab9e676..83b1c26f6 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -43,6 +43,8 @@ var NavigationType = { other: RCTWebViewManager.NavigationType.Other, }; +var JSNavigationScheme = RCTWebViewManager.JSNavigationScheme; + type ErrorEvent = { domain: any; code: any; @@ -75,6 +77,7 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( var WebView = React.createClass({ statics: { + JSNavigationScheme: JSNavigationScheme, NavigationType: NavigationType, }, @@ -86,7 +89,6 @@ var WebView = React.createClass({ bounces: PropTypes.bool, scrollEnabled: PropTypes.bool, automaticallyAdjustContentInsets: PropTypes.bool, - shouldInjectAJAXHandler: PropTypes.bool, contentInset: EdgeInsetsPropType, onNavigationStateChange: PropTypes.func, startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load @@ -95,6 +97,11 @@ var WebView = React.createClass({ * Used for android only, JS is enabled by default for WebView on iOS */ javaScriptEnabledAndroid: PropTypes.bool, + /** + * Used for iOS only, sets the JS to be injected when the webpage loads. + */ + injectedJavascriptIOS: PropTypes.string, + /** * Used for iOS only, sets whether the webpage scales to fit the view and the * user can change the scale @@ -152,9 +159,9 @@ var WebView = React.createClass({ style={webViewStyles} url={this.props.url} html={this.props.html} + injectedJavascriptIOS={this.props.injectedJavascriptIOS} bounces={this.props.bounces} scrollEnabled={this.props.scrollEnabled} - shouldInjectAJAXHandler={this.props.shouldInjectAJAXHandler} contentInset={this.props.contentInset} automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets} onLoadingStart={this.onLoadingStart} diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h index 52e7baaa3..90846e349 100644 --- a/React/Views/RCTWebView.h +++ b/React/Views/RCTWebView.h @@ -9,14 +9,16 @@ #import "RCTView.h" +extern NSString *const RCTJSNavigationScheme; + @class RCTEventDispatcher; @interface RCTWebView : RCTView @property (nonatomic, strong) NSURL *URL; @property (nonatomic, assign) UIEdgeInsets contentInset; -@property (nonatomic, assign) BOOL shouldInjectAJAXHandler; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; +@property (nonatomic, copy) NSString *injectedJavascriptIOS; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index 9e2fd3504..693f00eaa 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -18,6 +18,13 @@ #import "RCTView.h" #import "UIView+React.h" +// Special scheme that allow JS to notify the WebView to emit +// navigation event. +// +// JavaScript Example: +// window.location.href = 'react-js-navigation://hello' +NSString *const RCTJSNavigationScheme = @"react-js-navigation"; + @interface RCTWebView () @end @@ -26,6 +33,7 @@ { RCTEventDispatcher *_eventDispatcher; UIWebView *_webView; + NSString *_injectedJavascriptIOS; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -118,6 +126,19 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) return _webView.backgroundColor; } +- (void)setinjectedJavascriptIOS:(NSString *)jsStr +{ + if (_injectedJavascriptIOS == jsStr) { + return; + } + + if ([_injectedJavascriptIOS isEqualToString:jsStr]) { + return; + } + + _injectedJavascriptIOS = [jsStr copy]; +} + - (NSMutableDictionary *)baseEvent { NSURL *url = _webView.request.URL; @@ -136,7 +157,6 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) #pragma mark - UIWebViewDelegate methods -static NSString *const RCTJSAJAXScheme = @"react-ajax"; - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType @@ -152,8 +172,8 @@ static NSString *const RCTJSAJAXScheme = @"react-ajax"; [_eventDispatcher sendInputEventWithName:@"topLoadingStart" body:event]; } - // AJAX handler - return ![request.URL.scheme isEqualToString:RCTJSAJAXScheme]; + // JS Navigation handler + return ![request.URL.scheme isEqualToString:RCTJSNavigationScheme]; } - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error @@ -177,33 +197,8 @@ static NSString *const RCTJSAJAXScheme = @"react-ajax"; - (void)webViewDidFinishLoad:(UIWebView *)webView { - if (_shouldInjectAJAXHandler) { - - // From http://stackoverflow.com/questions/5353278/uiwebviewdelegate-not-monitoring-xmlhttprequest - - [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"\ - var s_ajaxListener = new Object(); \n\ - s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open; \n\ - s_ajaxListener.tempSend = XMLHttpRequest.prototype.send; \n\ - s_ajaxListener.callback = function() { \n\ - window.location.href = '%@://' + this.url; \n\ - } \n\ - XMLHttpRequest.prototype.open = function(a,b) { \n\ - s_ajaxListener.tempOpen.apply(this, arguments); \n\ - s_ajaxListener.method = a; \n\ - s_ajaxListener.url = b; \n\ - if (a.toLowerCase() === 'get') { \n\ - s_ajaxListener.data = (b.split('?'))[1]; \n\ - } \n\ - } \n\ - XMLHttpRequest.prototype.send = function(a,b) { \n\ - s_ajaxListener.tempSend.apply(this, arguments); \n\ - if (s_ajaxListener.method.toLowerCase() === 'post') { \n\ - s_ajaxListener.data = a; \n\ - } \n\ - s_ajaxListener.callback(); \n\ - } \n\ - ", RCTJSAJAXScheme]]; + if (_injectedJavascriptIOS != nil) { + [webView stringByEvaluatingJavaScriptFromString:_injectedJavascriptIOS]; } // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index a5de572bd..ff5fcf26a 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -27,14 +27,15 @@ RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL); RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString); RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL); RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL); +RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL); +RCT_EXPORT_VIEW_PROPERTY(injectedJavascriptIOS, NSString); RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets); RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL); -RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); -RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL); - (NSDictionary *)constantsToExport { return @{ + @"JSNavigationScheme": RCTJSNavigationScheme, @"NavigationType": @{ @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked), @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted), diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index df3ccfd7e..3877b3dd5 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllImmediates(); + jest.runAllTicks(); expect(fs.writeFile).toBeCalled(); }); });