diff --git a/.flowconfig b/.flowconfig index 56d38f30838ddc..31a4d53177816f 100644 --- a/.flowconfig +++ b/.flowconfig @@ -40,9 +40,9 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-3]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-3]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.12.0 +0.13.1 diff --git a/.travis.yml b/.travis.yml index 7da54bdf4076ac..eea5a4eaa1d8c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,13 +15,13 @@ install: - cp $(brew --prefix nvm)/nvm-exec .nvm/ - export NVM_DIR=.nvm - source $(brew --prefix nvm)/nvm.sh - - nvm install v0.10 + - nvm install iojs-v2 - npm config set spin=false - npm install script: - | - nvm use v0.10 + nvm use iojs-v2 if [ "$TEST_TYPE" = objc ] then diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 21819df4665f97..c0877d978a7c9b 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -26,6 +26,8 @@ var { } = React; var TimerMixin = require('react-timer-mixin'); +var invariant = require('invariant'); + var MovieCell = require('./MovieCell'); var MovieScreen = require('./MovieScreen'); @@ -73,18 +75,16 @@ var SearchScreen = React.createClass({ this.searchMovies(''); }, - _urlForQueryAndPage: function(query: string, pageNumber: ?number): string { + _urlForQueryAndPage: function(query: string, pageNumber: number): string { var apiKey = API_KEYS[this.state.queryNumber % API_KEYS.length]; if (query) { return ( - // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'movies.json?apikey=' + apiKey + '&q=' + encodeURIComponent(query) + '&page_limit=20&page=' + pageNumber ); } else { // With no query, load latest movies return ( - // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'lists/movies/in_theaters.json?apikey=' + apiKey + '&page_limit=20&page=' + pageNumber ); @@ -176,6 +176,7 @@ var SearchScreen = React.createClass({ }); var page = resultsCache.nextPageNumberForQuery[query]; + invariant(page != null, 'Next page number for "%s" is missing', query); fetch(this._urlForQueryAndPage(query, page)) .then((response) => response.json()) .catch((error) => { diff --git a/Examples/SampleApp/_flowconfig b/Examples/SampleApp/_flowconfig index 334ef74616f624..c2feaa128bb1b4 100644 --- a/Examples/SampleApp/_flowconfig +++ b/Examples/SampleApp/_flowconfig @@ -33,4 +33,4 @@ node_modules/react-native/Libraries/react-native/react-native-interface.js module.system=haste [version] -0.12.0 +0.13.1 diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index 86a3b96db7bd22..5c6ba8f9629e6e 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -17,11 +17,12 @@ var React = require('react-native'); var { + ActionSheetIOS, StyleSheet, Text, View, } = React; -var ActionSheetIOS = require('ActionSheetIOS'); + var BUTTONS = [ 'Button Index: 0', 'Button Index: 1', diff --git a/Examples/UIExplorer/AdSupportIOSExample.js b/Examples/UIExplorer/AdSupportIOSExample.js index ef7f076bfa8042..1626249e704699 100644 --- a/Examples/UIExplorer/AdSupportIOSExample.js +++ b/Examples/UIExplorer/AdSupportIOSExample.js @@ -15,10 +15,9 @@ */ 'use strict'; -var AdSupportIOS = require('AdSupportIOS'); - var React = require('react-native'); var { + AdSupportIOS, StyleSheet, Text, View, diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 0d1061acef49b8..5720175747593c 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -16,9 +16,9 @@ 'use strict'; var React = require('react-native'); -var StyleSheet = require('StyleSheet'); var { MapView, + StyleSheet, Text, TextInput, View, diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/TransformExample.js index a59a019b347f5a..9e848c3a57cb81 100644 --- a/Examples/UIExplorer/TransformExample.js +++ b/Examples/UIExplorer/TransformExample.js @@ -6,12 +6,14 @@ 'use strict'; var React = require('React'); +var { + StyleSheet, + View, +} = React; -var StyleSheet = require('StyleSheet'); var TimerMixin = require('react-timer-mixin'); -var UIExplorerBlock = require('UIExplorerBlock'); -var UIExplorerPage = require('UIExplorerPage'); -var View = require('View'); +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); var TransformExample = React.createClass({ diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js index 69767c4b741a38..154796cef76231 100644 --- a/Examples/UIExplorer/UIExplorerApp.android.js +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -17,14 +17,16 @@ 'use strict'; var React = require('react-native'); -var Dimensions = require('Dimensions'); -var DrawerLayoutAndroid = require('DrawerLayoutAndroid'); -var ToolbarAndroid = require('ToolbarAndroid'); -var UIExplorerList = require('./UIExplorerList'); var { + Dimensions, StyleSheet, View, } = React; +var UIExplorerList = require('./UIExplorerList'); + +// TODO: these should be exposed by the 'react-native' module. +var DrawerLayoutAndroid = require('DrawerLayoutAndroid'); +var ToolbarAndroid = require('ToolbarAndroid'); var DRAWER_WIDTH_LEFT = 56; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 5af5a32c648f92..1803c7ebd89fbf 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -21,6 +21,7 @@ var { ListView, PixelRatio, Platform, + Settings, StyleSheet, Text, TextInput, @@ -29,13 +30,20 @@ var { } = React; var { TestModule } = React.addons; -var Settings = require('Settings'); import type { ExampleModule } from 'ExampleTypes'; var createExamplePage = require('./createExamplePage'); var COMMON_COMPONENTS = [ + require('./ImageExample'), + require('./ListViewExample'), + require('./ListViewPagingExample'), + require('./MapViewExample'), + require('./Navigator/NavigatorExample'), + require('./ScrollViewExample'), + require('./TextInputExample'), + require('./TouchableExample'), require('./ViewExample'), require('./WebViewExample'), ]; @@ -51,23 +59,15 @@ if (Platform.OS === 'ios') { var COMPONENTS = COMMON_COMPONENTS.concat([ require('./ActivityIndicatorIOSExample'), require('./DatePickerIOSExample'), - require('./ImageExample'), - require('./ListViewExample'), - require('./ListViewPagingExample'), - require('./MapViewExample'), - require('./Navigator/NavigatorExample'), require('./NavigatorIOSColorsExample'), require('./NavigatorIOSExample'), require('./PickerIOSExample'), require('./ProgressViewIOSExample'), - require('./ScrollViewExample'), require('./SegmentedControlIOSExample'), require('./SliderIOSExample'), require('./SwitchIOSExample'), require('./TabBarIOSExample'), require('./TextExample.ios'), - require('./TextInputExample'), - require('./TouchableExample'), ]); var APIS = COMMON_APIS.concat([ diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index fe3cbef6f7d590..b9137e87cec584 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -16,7 +16,6 @@ 'use strict'; var React = require('react-native'); -var StyleSheet = require('StyleSheet'); var { StyleSheet, Text, diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 30d05210fb4a2f..9aad783b06595f 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -20,6 +20,12 @@ var Touchable = require('Touchable'); var merge = require('merge'); var onlyChild = require('onlyChild'); +var invariant = require('invariant'); +invariant( + AnimationExperimental || POPAnimation, + 'Please add the RCTAnimationExperimental framework to your project, or add //Libraries/FBReactKit:RCTPOPAnimation to your BUCK file if running internally within Facebook.' +); + type State = { animationID: ?number; }; diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 15ab9e676dec38..83b1c26f629de1 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/Libraries/Image/RCTImageDownloader.h b/Libraries/Image/RCTImageDownloader.h index 89a77975fdd872..5a4dd1987a32e4 100644 --- a/Libraries/Image/RCTImageDownloader.h +++ b/Libraries/Image/RCTImageDownloader.h @@ -14,7 +14,7 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error); @interface RCTImageDownloader : NSObject -+ (instancetype)sharedInstance; ++ (RCTImageDownloader *)sharedInstance; /** * Downloads a block of raw data and returns it. Note that the callback block diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 760fce614d0dfe..a8a68c0b472870 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -9,25 +9,27 @@ #import "RCTImageDownloader.h" -#import "RCTCache.h" #import "RCTLog.h" #import "RCTUtils.h" typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error); +CGSize RCTTargetSizeForClipRect(CGRect); +CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); + @implementation RCTImageDownloader { - RCTCache *_cache; + NSURLCache *_cache; dispatch_queue_t _processingQueue; NSMutableDictionary *_pendingBlocks; } -+ (instancetype)sharedInstance ++ (RCTImageDownloader *)sharedInstance { static RCTImageDownloader *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] init]; + sharedInstance = [[RCTImageDownloader alloc] init]; }); return sharedInstance; } @@ -35,27 +37,22 @@ + (instancetype)sharedInstance - (instancetype)init { if ((self = [super init])) { - _cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"]; + _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"]; _processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL); _pendingBlocks = [[NSMutableDictionary alloc] init]; } - return self; -} -static NSString *RCTCacheKeyForURL(NSURL *url) -{ - return url.absoluteString; + return self; } - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block { - NSString *cacheKey = RCTCacheKeyForURL(url); + NSString *cacheKey = url.absoluteString; __block BOOL cancelled = NO; __block NSURLSessionDataTask *task = nil; dispatch_block_t cancel = ^{ - cancelled = YES; dispatch_async(_processingQueue, ^{ @@ -88,21 +85,28 @@ - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block }); }; - if ([_cache hasDataForKey:cacheKey]) { - [_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) { - if (!cancelled) { - runBlocks(YES, data, nil); - } - }]; - } else { - task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (!cancelled) { - runBlocks(NO, data, error); - } - }]; + task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!cancelled) { + runBlocks(NO, data, error); + } - [task resume]; - } + RCTImageDownloader *strongSelf = weakSelf; + NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed]; + [strongSelf->_cache storeCachedResponse:cachedResponse forDataTask:task]; + task = nil; + }]; + + [_cache getCachedResponseForDataTask:task completionHandler:^(NSCachedURLResponse *cachedResponse) { + if (cancelled) { + return; + } + + if (cachedResponse) { + runBlocks(YES, cachedResponse.data, nil); + } else { + [task resume]; + } + }]; } }); @@ -111,22 +115,78 @@ - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block - (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block { - NSString *cacheKey = RCTCacheKeyForURL(url); - __weak RCTImageDownloader *weakSelf = self; return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) { - if (!cached) { - RCTImageDownloader *strongSelf = weakSelf; - [strongSelf->_cache setData:data forKey:cacheKey]; - } block(data, error); }]; } +- (id)downloadImageForURL:(NSURL *)url + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + backgroundColor:(UIColor *)backgroundColor + block:(RCTImageDownloadBlock)block +{ + return [self downloadDataForURL:url block:^(NSData *data, NSError *error) { + if (!data || error) { + block(nil, error); + return; + } + + if (CGSizeEqualToSize(size, CGSizeZero)) { + // Target size wasn't available yet, so abort image drawing + block(nil, nil); + return; + } + + UIImage *image = [UIImage imageWithData:data scale:scale]; + if (image) { + + // Get scale and size + CGFloat destScale = scale ?: RCTScreenScale(); + CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode); + CGSize destSize = RCTTargetSizeForClipRect(imageRect); + + // Opacity optimizations + UIColor *blendColor = nil; + BOOL opaque = !RCTImageHasAlpha(image.CGImage); + if (!opaque && backgroundColor) { + CGFloat alpha; + [backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha]; + if (alpha > 0.999) { // no benefit to blending if background is translucent + opaque = YES; + blendColor = backgroundColor; + } + } + + // Decompress image at required size + UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale); + if (blendColor) { + [blendColor setFill]; + UIRectFill((CGRect){CGPointZero, destSize}); + } + [image drawInRect:imageRect]; + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + + block(image, nil); + }]; +} + +- (void)cancelDownload:(id)downloadToken +{ + if (downloadToken) { + ((dispatch_block_t)downloadToken)(); + } +} + +@end + /** * Returns the optimal context size for an image drawn using the clip rect * returned by RCTClipRect. */ -CGSize RCTTargetSizeForClipRect(CGRect); CGSize RCTTargetSizeForClipRect(CGRect clipRect) { return (CGSize){ @@ -141,7 +201,6 @@ CGSize RCTTargetSizeForClipRect(CGRect clipRect) * then calculates the optimal rectangle to draw the image into so that it will * be sized and positioned correctly if drawn using the specified content mode. */ -CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, CGSize destSize, CGFloat destScale, UIViewContentMode resizeMode) @@ -202,66 +261,3 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, return (CGRect){CGPointZero, destSize}; } } - -- (id)downloadImageForURL:(NSURL *)url - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(UIViewContentMode)resizeMode - backgroundColor:(UIColor *)backgroundColor - block:(RCTImageDownloadBlock)block -{ - return [self downloadDataForURL:url block:^(NSData *data, NSError *error) { - - if (!data || error) { - block(nil, error); - return; - } - - if (CGSizeEqualToSize(size, CGSizeZero)) { - // Target size wasn't available yet, so abort image drawing - block(nil, nil); - return; - } - - UIImage *image = [UIImage imageWithData:data scale:scale]; - if (image) { - - // Get scale and size - CGFloat destScale = scale ?: RCTScreenScale(); - CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode); - CGSize destSize = RCTTargetSizeForClipRect(imageRect); - - // Opacity optimizations - UIColor *blendColor = nil; - BOOL opaque = !RCTImageHasAlpha(image.CGImage); - if (!opaque && backgroundColor) { - CGFloat alpha; - [backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha]; - if (alpha > 0.999) { // no benefit to blending if background is translucent - opaque = YES; - blendColor = backgroundColor; - } - } - - // Decompress image at required size - UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale); - if (blendColor) { - [blendColor setFill]; - UIRectFill((CGRect){CGPointZero, destSize}); - } - [image drawInRect:imageRect]; - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } - block(image, nil); - }]; -} - -- (void)cancelDownload:(id)downloadToken -{ - if (downloadToken) { - ((dispatch_block_t)downloadToken)(); - } -} - -@end diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js index 098dfcee9726ab..37625978adc142 100644 --- a/Libraries/Interaction/InteractionManager.js +++ b/Libraries/Interaction/InteractionManager.js @@ -21,11 +21,6 @@ var setImmediate = require('setImmediate'); type Handle = number; -/** - * Maximum time a handle can be open before warning in DEV. - */ -var DEV_TIMEOUT = 2000; - var _emitter = new EventEmitter(); var _interactionSet = new Set(); var _addInteractionSet = new Set(); @@ -94,14 +89,6 @@ var InteractionManager = { scheduleUpdate(); var handle = ++_inc; _addInteractionSet.add(handle); - if (__DEV__) { - // Capture the stack trace of what created the handle. - var error = new Error( - 'InteractionManager: interaction handle not cleared within ' + - DEV_TIMEOUT + ' ms.' - ); - setDevTimeoutHandle(handle, error, DEV_TIMEOUT); - } return handle; }, @@ -166,19 +153,4 @@ function processUpdate() { _deleteInteractionSet.clear(); } -/** - * Wait until `timeout` has passed and warn if the handle has not been cleared. - */ -function setDevTimeoutHandle( - handle: Handle, - error: Error, - timeout: number -): void { - setTimeout(() => { - if (_interactionSet.has(handle)) { - console.warn(error.message + '\n' + error.stack); - } - }, timeout); -} - module.exports = InteractionManager; diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 54c44513e3206e..9ef60c61175e2c 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -55,7 +55,7 @@ - (instancetype)init }]; } -RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(id)body) +RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(nullable id)body) { [_bridge.eventDispatcher sendAppEventWithName:name body:body]; } diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 36727656739638..42cf30f51d7784 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -42,11 +42,14 @@ var ReactNative = Object.assign(Object.create(require('React')), { WebView: require('WebView'), // APIs + ActionSheetIOS: require('ActionSheetIOS'), + AdSupportIOS: require('AdSupportIOS'), AlertIOS: require('AlertIOS'), AppRegistry: require('AppRegistry'), AppStateIOS: require('AppStateIOS'), AsyncStorage: require('AsyncStorage'), CameraRoll: require('CameraRoll'), + Dimensions: require('Dimensions'), ImagePickerIOS: require('ImagePickerIOS'), InteractionManager: require('InteractionManager'), LayoutAnimation: require('LayoutAnimation'), @@ -55,6 +58,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { PanResponder: require('PanResponder'), PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), + Settings: require('Settings'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), VibrationIOS: require('VibrationIOS'), diff --git a/React.podspec b/React.podspec index c7bfbbd7c968e1..8c499281790e76 100644 --- a/React.podspec +++ b/React.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| s.default_subspec = 'Core' s.requires_arc = true s.platform = :ios, "7.0" - s.prepare_command = 'npm install' + s.prepare_command = 'npm install --production' s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli" s.header_mappings_dir = "." diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 838e8bc80295d1..b1bb6cf1aa74d8 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -219,6 +219,9 @@ - (void)registerModules [_frameUpdateObservers addObject:moduleData]; } } + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules + object:self]; } - (void)initJS @@ -721,7 +724,7 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); dispatch_async(dispatch_get_main_queue(), ^{ - [self.perfStats.jsGraph tick:displayLink.timestamp]; + [self.perfStats.jsGraph onTick:displayLink.timestamp]; }); } @@ -731,7 +734,7 @@ - (void)_mainThreadUpdate:(CADisplayLink *)displayLink RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); - [self.perfStats.uiGraph tick:displayLink.timestamp]; + [self.perfStats.uiGraph onTick:displayLink.timestamp]; } - (void)startProfiling diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 1d70a6367eda15..f12f26158c9c5b 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -33,6 +33,11 @@ RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification; */ RCT_EXTERN NSString *const RCTJavaScriptDidFailToLoadNotification; +/** + * This notification fires when the bridge created all registered native modules + */ +RCT_EXTERN NSString *const RCTDidCreateNativeModules; + /** * This block can be used to instantiate modules that require additional * init parameters, or additional configuration prior to being used. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 28fcc460f2041b..c6baf82a6207df 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -20,6 +20,7 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; +NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; @class RCTBatchedBridge; diff --git a/React/Base/RCTCache.h b/React/Base/RCTCache.h deleted file mode 100644 index 8704ff3db342b2..00000000000000 --- a/React/Base/RCTCache.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -@interface RCTCache : NSObject - -- (instancetype)init; // name = @"default" -- (instancetype)initWithName:(NSString *)name; - -@property (nonatomic, assign) NSUInteger maximumDiskSize; // in bytes - -#pragma mark - Retrieval - -- (BOOL)hasDataForKey:(NSString *)key; -- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *data))completionHandler; - -#pragma mark - Insertion - -- (void)setData:(NSData *)data forKey:(NSString *)key; -- (void)removeAllData; - -@end diff --git a/React/Base/RCTCache.m b/React/Base/RCTCache.m deleted file mode 100644 index 9390069c432713..00000000000000 --- a/React/Base/RCTCache.m +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTCache.h" - -#import -#import - -#import "RCTAssert.h" - -static NSString *const RCTCacheSubdirectoryName = @"React"; -static NSString *const RCTKeyExtendedAttributeName = @"com.facebook.React.RCTCacheManager.Key"; -static NSMapTable *RCTLivingCachesByName; - -static NSError *RCTPOSIXError(int errorNumber) -{ - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: @(strerror(errorNumber)) - }; - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo]; -} - -static NSString *RCTGetExtendedAttribute(NSURL *fileURL, NSString *key, NSError **error) -{ - const char *path = fileURL.fileSystemRepresentation; - ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0); - if (length <= 0) { - if (error) *error = RCTPOSIXError(errno); - return nil; - } - - char *buffer = malloc(length); - length = getxattr(path, key.UTF8String, buffer, length, 0, 0); - if (length > 0) { - return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES]; - } - - free(buffer); - if (error) *error = RCTPOSIXError(errno); - return nil; -} - -static BOOL RCTSetExtendedAttribute(NSURL *fileURL, NSString *key, NSString *value, NSError **error) -{ - const char *path = fileURL.fileSystemRepresentation; - - int result; - if (value) { - const char *valueUTF8String = value.UTF8String; - result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0); - } else { - result = removexattr(path, key.UTF8String, 0); - } - - if (result) { - if (error) *error = RCTPOSIXError(errno); - return NO; - } - - return YES; -} - -#pragma mark - Cache Record - - -@interface RCTCacheRecord : NSObject - -@property (readonly) NSUUID *UUID; -@property (readonly, weak) dispatch_queue_t queue; -@property (nonatomic, copy) NSData *data; - -@end - -@implementation RCTCacheRecord - -- (instancetype)initWithUUID:(NSUUID *)UUID -{ - if ((self = [super init])) { - _UUID = [UUID copy]; - } - return self; -} - -- (void)enqueueBlock:(dispatch_block_t)block -{ - dispatch_queue_t queue = _queue; - if (!queue) { - NSString *queueName = [NSString stringWithFormat:@"com.facebook.React.RCTCache.%@", _UUID.UUIDString]; - queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL); - _queue = queue; - } - - dispatch_async(queue, block); -} - -@end - -#pragma mark - Cache - -@implementation RCTCache -{ - NSString *_name; - NSFileManager *_fileManager; - NSMutableDictionary *_storage; - NSURL *_cacheDirectoryURL; -} - -+ (void)initialize -{ - if (self == [RCTCache class]) { - RCTLivingCachesByName = [NSMapTable strongToWeakObjectsMapTable]; - } -} - -- (instancetype)init -{ - return [self initWithName:@"default"]; -} - -- (instancetype)initWithName:(NSString *)name -{ - RCTAssertParam(name); - RCTAssert(name.length < NAME_MAX, @"Name must be fewer than %i characters in length.", NAME_MAX); - RCTCache *cachedCache = [RCTLivingCachesByName objectForKey:name]; - if (cachedCache) { - self = cachedCache; - return self; - } - - if ((self = [super init])) { - _name = [name copy]; - _fileManager = [[NSFileManager alloc] init]; - _storage = [NSMutableDictionary dictionary]; - - NSURL *cacheDirectoryURL = [[_fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:RCTCacheSubdirectoryName isDirectory:YES]; - _cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:name isDirectory:YES]; - [_fileManager createDirectoryAtURL:_cacheDirectoryURL withIntermediateDirectories:YES attributes:nil error:NULL]; - - NSArray *fileURLs = [_fileManager contentsOfDirectoryAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL]; - for (NSURL *fileURL in fileURLs) { - NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent]; - if (!UUID) continue; - - NSString *key = RCTGetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, NULL); - if (!key) { - [_fileManager removeItemAtURL:fileURL error:NULL]; - continue; - } - - _storage[key] = [[RCTCacheRecord alloc] initWithUUID:UUID]; - } - } - return self; -} - -- (BOOL)hasDataForKey:(NSString *)key -{ - return _storage[key] != nil; -} - -- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *))completionHandler -{ - NSParameterAssert(key.length > 0); - NSParameterAssert(completionHandler != nil); - RCTCacheRecord *record = _storage[key]; - if (!record) { - completionHandler(nil); - return; - } - - [record enqueueBlock:^{ - if (!record.data) { - record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]]; - } - completionHandler(record.data); - }]; -} - -- (void)setData:(NSData *)data forKey:(NSString *)key -{ - NSParameterAssert(key.length > 0); - RCTCacheRecord *record = _storage[key]; - if (!record) { - if (!data) return; - - record = [[RCTCacheRecord alloc] initWithUUID:[NSUUID UUID]]; - _storage[key] = record; - } - - NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]; - - UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; - [record enqueueBlock:^{ - if (data) { - [data writeToURL:fileURL options:NSDataWritingAtomic error:NULL]; - RCTSetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, key, NULL); - } else { - [_fileManager removeItemAtURL:fileURL error:NULL]; - } - - if (identifier != UIBackgroundTaskInvalid) { - [[UIApplication sharedApplication] endBackgroundTask:identifier]; - } - }]; -} - -- (void)removeAllData -{ - UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; - dispatch_group_t group = dispatch_group_create(); - - for (RCTCacheRecord *record in _storage.allValues) { - NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]; - dispatch_group_async(group, record.queue, ^{ - [_fileManager removeItemAtURL:fileURL error:NULL]; - }); - } - - if (identifier != UIBackgroundTaskInvalid) { - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] endBackgroundTask:identifier]; - }); - } - - [_storage removeAllObjects]; -} - -@end diff --git a/React/Base/RCTFPSGraph.h b/React/Base/RCTFPSGraph.h index 905829aba2be7b..0c0e2664a844c6 100644 --- a/React/Base/RCTFPSGraph.h +++ b/React/Base/RCTFPSGraph.h @@ -18,6 +18,6 @@ typedef NS_ENUM(NSUInteger, RCTFPSGraphPosition) { - (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color NS_DESIGNATED_INITIALIZER; -- (void)tick:(NSTimeInterval)timestamp; +- (void)onTick:(NSTimeInterval)timestamp; @end diff --git a/React/Base/RCTFPSGraph.m b/React/Base/RCTFPSGraph.m index 5e9b0d855e3c75..1aa5efd7dd4fe4 100644 --- a/React/Base/RCTFPSGraph.m +++ b/React/Base/RCTFPSGraph.m @@ -93,7 +93,7 @@ - (UILabel *)createLabel:(UIColor *)color return label; } -- (void)tick:(NSTimeInterval)timestamp +- (void)onTick:(NSTimeInterval)timestamp { _frameCount++; if (_prevTime == -1) { diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 146247009a2171..fa5afc5cb8f762 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -67,16 +67,5 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); @end -static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; -__used static void RCTSetExecutorID(id executor) -{ - static NSUInteger executorID = 0; - if (executor) { - objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); - } -} - -__used static NSNumber *RCTGetExecutorID(id executor) -{ - return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0; -} +void RCTSetExecutorID(id executor); +NSNumber *RCTGetExecutorID(id executor); diff --git a/React/Base/RCTJavaScriptExecutor.m b/React/Base/RCTJavaScriptExecutor.m new file mode 100644 index 00000000000000..04e3db433ba643 --- /dev/null +++ b/React/Base/RCTJavaScriptExecutor.m @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTJavaScriptExecutor.h" + + +static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; + +void RCTSetExecutorID(id executor) +{ + static NSUInteger executorID = 0; + if (executor) { + objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); + } +} + +NSNumber *RCTGetExecutorID(id executor) +{ + return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0; +} diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index aca8267b94f56f..f294085e2f10cd 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -40,10 +40,12 @@ - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName static NSRegularExpression *typeRegex; static NSRegularExpression *selectorRegex; if (!typeRegex) { - NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))"; + NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))"; NSString *constPattern = @"(?:const)"; - NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern]; - NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern]; + NSString *nullabilityPattern = @"(?:__nullable|__nonnull|nullable|nonnull)"; + NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|%@)\\s*)", + unusedPattern, constPattern, nullabilityPattern]; + NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", annotationPattern]; typeRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL]; selectorRegex = [[NSRegularExpression alloc] initWithPattern:@"(?<=:).*?(?=[a-zA-Z_]+:|$)" options:0 error:NULL]; diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 099c120a03a4fc..54261357db451e 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -65,8 +65,8 @@ 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; }; + 783ABB351B38A9D3003FFD95 /* RCTJavaScriptExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; - 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; }; 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; }; @@ -216,11 +216,10 @@ 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; 63F014BE1B02080B003B75D2 /* RCTPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointAnnotation.h; sourceTree = ""; }; 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPointAnnotation.m; sourceTree = ""; }; + 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptExecutor.m; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; - 830BA4531A8E3BDA00D53203 /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = ""; }; - 830BA4541A8E3BDA00D53203 /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = ""; }; 83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = ""; }; 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = ""; }; 83CBBA2E1A601D0E00E9B192 /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -425,8 +424,6 @@ 83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */, 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */, 830213F31A654E0800B993E6 /* RCTBridgeModule.h */, - 830BA4531A8E3BDA00D53203 /* RCTCache.h */, - 830BA4541A8E3BDA00D53203 /* RCTCache.m */, 83CBBACA1A6023D300E9B192 /* RCTConvert.h */, 83CBBACB1A6023D300E9B192 /* RCTConvert.m */, 13AF1F851AE6E777005F5298 /* RCTDefines.h */, @@ -434,6 +431,7 @@ 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */, + 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */, 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */, 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */, 83CBBA4D1A601E3B00E9B192 /* RCTLog.h */, @@ -579,6 +577,7 @@ 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */, 14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, + 783ABB351B38A9D3003FFD95 /* RCTJavaScriptExecutor.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, @@ -601,7 +600,6 @@ 131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */, 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, - 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, 00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */, 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */, diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h index 52e7baaa3a6dad..90846e34920cbb 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 9e2fd3504881f6..693f00eaa076c7 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 @@ @implementation RCTWebView { RCTEventDispatcher *_eventDispatcher; UIWebView *_webView; + NSString *_injectedJavascriptIOS; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -118,6 +126,19 @@ - (UIColor *)backgroundColor 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 @@ - (NSMutableDictionary *)baseEvent #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 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR [_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 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er - (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 a5de572bd57aa8..ff5fcf26a2b85b 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -27,14 +27,15 @@ - (UIView *)view 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/package.json b/package.json index 13c57fbebde27c..41c6b586931b1d 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "yargs": "1.3.2" }, "devDependencies": { - "jest-cli": "0.4.5", + "jest-cli": "facebook/jest#0.5.x", "babel-eslint": "3.1.5", "eslint": "0.21.2", "eslint-plugin-react": "2.3.0" diff --git a/packager/blacklist.js b/packager/blacklist.js index a2ba71673af5a2..237691a85e9491 100644 --- a/packager/blacklist.js +++ b/packager/blacklist.js @@ -8,6 +8,8 @@ */ 'use strict'; +var path = require('path'); + // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ @@ -24,7 +26,7 @@ var platformBlacklists = { ios: [ 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.android.js', ], @@ -32,14 +34,16 @@ var platformBlacklists = { 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', 'node_modules/react-tools/src/browser/ReactTextComponent.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.ios.js', ], }; function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + var escaped = str.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + // convert the '/' into an escaped local file separator + return escaped.replace(/\//g,'\\' + path.sep); } function blacklist(platform, additionalBlacklist) { diff --git a/packager/package.json b/packager/package.json index f3af007f350581..cc3f4fc6f969e5 100644 --- a/packager/package.json +++ b/packager/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.4.5", + "jest-cli": "git://github.com/facebook/jest#0.5.x", "eslint": "0.9.2" } } diff --git a/packager/packager.js b/packager/packager.js index f2d526e06bcbb7..ff0faa315b3aa4 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -12,6 +12,7 @@ var fs = require('fs'); var path = require('path'); var execFile = require('child_process').execFile; var http = require('http'); +var isAbsolutePath = require('absolute-path'); var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); @@ -56,6 +57,11 @@ var options = parseCommandLine([{ }, { command: 'nonPersistent', description: 'Disable file watcher' +}, { + command: 'transformer', + type: 'string', + default: require.resolve('./transformer.js'), + description: 'Specify a custom transformer to be used (absolute path)' }]); if (options.projectRoots) { @@ -63,8 +69,9 @@ if (options.projectRoots) { options.projectRoots = options.projectRoots.split(','); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { - // packager is running from node_modules of another project + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { + // packager is running from node_modules of another project options.projectRoots = [path.resolve(__dirname, '../../..')]; } else if (__dirname.match(/Pods\/React\/packager$/)) { // packager is running from node_modules of another project @@ -91,7 +98,8 @@ if (options.assetRoots) { }); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; } else if (__dirname.match(/Pods\/React\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; @@ -208,12 +216,17 @@ function statusPageMiddleware(req, res, next) { } function getAppMiddleware(options) { + var transformerPath = options.transformer; + if (!isAbsolutePath(transformerPath)) { + transformerPath = path.resolve(process.cwd(), transformerPath); + } + return ReactPackager.middleware({ nonPersistent: options.nonPersistent, projectRoots: options.projectRoots, blacklistRE: blacklist(options.platform), cacheVersion: '2', - transformModulePath: require.resolve('./transformer.js'), + transformModulePath: transformerPath, assetRoots: options.assetRoots, assetExts: ['png', 'jpeg', 'jpg'], polyfillModuleNames: [ diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index d4ea0dd391fbce..c47d762a1203b5 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -8,7 +8,7 @@ */ 'use strict'; -require('babel/register')({ +require('babel-core/register')({ only: /react-packager\/src/ }); diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 673d9c58a9ea3b..471e6069189ac4 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2353,7 +2353,8 @@ describe('DependencyGraph', function() { } callbacks.push(callback); return this; - } + }, + isWatchman: () => Promise.resolve(false), }; }); diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 8145bfa03ceb5b..42c1a485f33044 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -29,6 +29,7 @@ const validateOpts = declareOpts({ }, ignoreFilePath: { type: 'function', + default: function(){} }, fileWatcher: { diff --git a/packager/react-packager/src/DependencyResolver/crawlers/index.js b/packager/react-packager/src/DependencyResolver/crawlers/index.js index 71290af44741c0..fe755bcb621e13 100644 --- a/packager/react-packager/src/DependencyResolver/crawlers/index.js +++ b/packager/react-packager/src/DependencyResolver/crawlers/index.js @@ -1,21 +1,11 @@ 'use strict'; const nodeCrawl = require('./node'); -//const watchmanCrawl = require('./watchman'); +const watchmanCrawl = require('./watchman'); function crawl(roots, options) { - return nodeCrawl(roots, options); - - // Although, in theory, watchman should be much faster; - // there is currently a bottleneck somewhere in the - // encoding/decoding that is causing it to be slower - // than node crawling. However, this should be fixed soon. - // https://github.com/facebook/watchman/issues/113 - /* const {fileWatcher} = options; return fileWatcher.isWatchman().then(isWatchman => { - - console.log(isWatchman); if (!isWatchman) { return false; } @@ -30,7 +20,7 @@ function crawl(roots, options) { } return nodeCrawl(roots, options); - });*/ + }); } module.exports = crawl; diff --git a/packager/react-packager/src/DependencyResolver/crawlers/watchman.js b/packager/react-packager/src/DependencyResolver/crawlers/watchman.js index d6479a513f008b..1871e3ead8b554 100644 --- a/packager/react-packager/src/DependencyResolver/crawlers/watchman.js +++ b/packager/react-packager/src/DependencyResolver/crawlers/watchman.js @@ -36,7 +36,7 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { } } - const cmd = Promise.promisify(watcher.client.command.bind(watcher.client)); + const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client)); return cmd(['query', watchedRoot, { 'suffix': exts, 'expression': ['allof', ['type', 'f'], 'exists', dirExpr], diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index 46b4667e72cefa..d9ed7f32fd2519 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -12,9 +12,11 @@ const EventEmitter = require('events').EventEmitter; const sane = require('sane'); const Promise = require('promise'); const exec = require('child_process').exec; +const _ = require('underscore'); const MAX_WAIT_TIME = 25000; +// TODO(amasad): can we use watchman version command instead?r const detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -79,9 +81,11 @@ class FileWatcher extends EventEmitter { static createDummyWatcher() { const ev = new EventEmitter(); - ev.end = function() { - return Promise.resolve(); - }; + _.extend(ev, { + isWatchman: () => Promise.resolve(false), + end: () => Promise.resolve(), + }); + return ev; } } diff --git a/packager/transformer.js b/packager/transformer.js index 9096cc879fbd79..c50bdd3142eee1 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -10,7 +10,7 @@ */ 'use strict'; -var babel = require('babel'); +var babel = require('babel-core'); function transform(srcTxt, filename, options) { var result = babel.transform(srcTxt, {