From a2db4a4a5bb579848e0a9504a12f81d599895b53 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 1 Jun 2015 08:28:31 -0700 Subject: [PATCH 01/30] Removed redundant JSON encode/decode from RCTDataManager Summary: @public For some reason we were manually JSON-encoding the RCTDataManager responses, and then decoding them again on the JS side. Since all data sent over the bridge is JSON-encoded anyway, this is pretty pointless. Test Plan: * Test Movies app in OSS, which uses RCTDataManager * Test any code that uses RKHTTPQueryGenericExecutor to make network requests (e.g. Groups) * Test the Groups photo upload feature, which uses RKHTTPQueryWithImageUploadExecutor --- Examples/Movies/Movies/main.m | 2 +- Libraries/Network/RCTDataManager.m | 35 +++++++++++++------------ Libraries/Network/XMLHttpRequest.ios.js | 5 +--- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/Examples/Movies/Movies/main.m b/Examples/Movies/Movies/main.m index 8954f343c..9c58a39a4 100644 --- a/Examples/Movies/Movies/main.m +++ b/Examples/Movies/Movies/main.m @@ -17,6 +17,6 @@ int main(int argc, char * argv[]) { @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTDataManager.m index 1d0a793de..6a2e39b2d 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTDataManager.m @@ -39,34 +39,35 @@ RCT_EXPORT_METHOD(queryData:(NSString *)queryType // Build data task NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) { + NSHTTPURLResponse *httpResponse = nil; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + // Might be a local file request + httpResponse = (NSHTTPURLResponse *)response; + } + // Build response - NSDictionary *responseJSON; + NSArray *responseJSON; if (connectionError == nil) { NSStringEncoding encoding = NSUTF8StringEncoding; if (response.textEncodingName) { CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); } - NSHTTPURLResponse *httpResponse = nil; - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - // Might be a local file request - httpResponse = (NSHTTPURLResponse *)response; - } - responseJSON = @{ - @"status": @([httpResponse statusCode] ?: 200), - @"responseHeaders": [httpResponse allHeaderFields] ?: @{}, - @"responseText": [[NSString alloc] initWithData:data encoding:encoding] ?: @"" - }; + responseJSON = @[ + @(httpResponse.statusCode ?: 200), + httpResponse.allHeaderFields ?: @{}, + [[NSString alloc] initWithData:data encoding:encoding] ?: @"", + ]; } else { - responseJSON = @{ - @"status": @0, - @"responseHeaders": @{}, - @"responseText": [connectionError localizedDescription] ?: [NSNull null] - }; + responseJSON = @[ + @(httpResponse.statusCode), + httpResponse.allHeaderFields ?: @{}, + connectionError.localizedDescription ?: [NSNull null], + ]; } // Send response (won't be sent on same thread as caller) - responseSender(@[RCTJSONStringify(responseJSON, NULL)]); + responseSender(responseJSON); }]; diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 6c7367c18..64b5ea526 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -30,10 +30,7 @@ class XMLHttpRequest extends XMLHttpRequestBase { }, // TODO: Do we need this? is it used anywhere? 'h' + crc32(method + '|' + url + '|' + data), - (result) => { - result = JSON.parse(result); - this.callback(result.status, result.responseHeaders, result.responseText); - } + this.callback.bind(this) ); } From 49e87af934b8060858a8b0e3e1b03b690470b1e8 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 1 Jun 2015 08:34:09 -0700 Subject: [PATCH 02/30] Fixed text update on OSS --- Libraries/Text/RCTShadowText.h | 1 - Libraries/Text/RCTShadowText.m | 41 +++++++++++++++++++++++++++++++++ Libraries/Text/RCTTextManager.m | 2 -- React/Modules/RCTUIManager.m | 4 ++++ React/Views/RCTShadowView.h | 37 +++++++++++++++++++---------- React/Views/RCTShadowView.m | 30 +++++++++++++++--------- 6 files changed, 89 insertions(+), 26 deletions(-) diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 189bff79e..d156bb4d6 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -28,7 +28,6 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, strong) UIColor *textBackgroundColor; @property (nonatomic, assign) NSWritingDirection writingDirection; -- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width; - (void)recomputeText; @end diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 7e1daf908..511697f89 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -12,6 +12,8 @@ #import "RCTConvert.h" #import "RCTLog.h" #import "RCTShadowRawText.h" +#import "RCTSparseArray.h" +#import "RCTText.h" #import "RCTUtils.h" NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName"; @@ -19,6 +21,8 @@ NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName"; @implementation RCTShadowText { + NSTextStorage *_cachedTextStorage; + CGFloat _cachedTextStorageWidth; NSAttributedString *_cachedAttributedString; CGFloat _effectiveLetterSpacing; } @@ -50,8 +54,35 @@ static css_dim_t RCTMeasure(void *context, float width) return self; } +- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties +{ + parentProperties = [super processUpdatedProperties:applierBlocks + parentProperties:parentProperties]; + + NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width]; + [applierBlocks addObject:^(RCTSparseArray *viewRegistry) { + RCTText *view = viewRegistry[self.reactTag]; + view.textStorage = textStorage; + }]; + + return parentProperties; +} + +- (void)applyLayoutNode:(css_node_t *)node + viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame + absolutePosition:(CGPoint)absolutePosition +{ + [super applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; + [self dirtyPropagation]; +} + - (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width { + if (_cachedTextStorage && width == _cachedTextStorageWidth) { + return _cachedTextStorage; + } + NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString]; @@ -69,13 +100,23 @@ static css_dim_t RCTMeasure(void *context, float width) [layoutManager addTextContainer:textContainer]; [layoutManager ensureLayoutForTextContainer:textContainer]; + _cachedTextStorage = textStorage; + _cachedTextStorageWidth = width; + return textStorage; } +- (void)dirtyText +{ + [super dirtyText]; + _cachedTextStorage = nil; +} + - (void)recomputeText { [self attributedString]; [self setTextComputed]; + [self dirtyPropagation]; } - (NSAttributedString *)attributedString diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index d9e547c77..26c6329e2 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -96,12 +96,10 @@ RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger) { NSNumber *reactTag = shadowView.reactTag; UIEdgeInsets padding = shadowView.paddingAsInsets; - NSTextStorage *textStorage = [shadowView buildTextStorageForWidth:shadowView.frame.size.width]; return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTText *text = viewRegistry[reactTag]; text.contentInset = padding; - text.textStorage = textStorage; }; } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 127cbd9fc..76e253a76 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -481,6 +481,10 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa shadowView.newView = NO; } + // These are blocks to be executed on each view, immediately after + // reactSetFrame: has been called. Note that if reactSetFrame: is not called, + // these won't be called either, so this is not a suitable place to update + // properties that aren't related to layout. NSMutableArray *updateBlocks = [[NSMutableArray alloc] init]; for (RCTShadowView *shadowView in viewsWithNewFrames) { RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag]; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index c2e10750d..1c44033f6 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -20,8 +20,7 @@ typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) { RCTUpdateLifecycleDirtied, }; -// TODO: is this redundact now? -typedef void (^RCTApplierBlock)(RCTSparseArray *); +typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry); /** * ShadowView tree mirrors RCT view tree. Every node is highly stateful. @@ -117,34 +116,48 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *); * The applierBlocks set contains RCTApplierBlock functions that must be applied * on the main thread in order to update the view. */ -- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties; +- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties; + +/** + * Process the updated properties and apply them to view. Shadow view classes + * that add additional propagating properties should override this method. + */ +- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties NS_REQUIRES_SUPER; /** * Calculate all views whose frame needs updating after layout has been calculated. * The viewsWithNewFrame set contains the reactTags of the views that need updating. */ -- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint; +- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame + parentConstraint:(CGSize)parentConstraint; + +/** + * Recursively apply layout to children. + */ +- (void)applyLayoutNode:(css_node_t *)node + viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame + absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER; /** * The following are implementation details exposed to subclasses. Do not call them directly */ -- (void)fillCSSNode:(css_node_t *)node; -- (void)dirtyLayout; +- (void)fillCSSNode:(css_node_t *)node NS_REQUIRES_SUPER; +- (void)dirtyLayout NS_REQUIRES_SUPER; - (BOOL)isLayoutDirty; -// TODO: is this still needed? -- (void)dirtyPropagation; +- (void)dirtyPropagation NS_REQUIRES_SUPER; - (BOOL)isPropagationDirty; -// TODO: move this to text node? -- (void)dirtyText; +- (void)dirtyText NS_REQUIRES_SUPER; +- (void)setTextComputed NS_REQUIRES_SUPER; - (BOOL)isTextDirty; -- (void)setTextComputed; /** * Triggers a recalculation of the shadow view's layout. */ -- (void)updateLayout; +- (void)updateLayout NS_REQUIRES_SUPER; /** * Computes the recursive offset, meaning the sum of all descendant offsets - diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index b26193f24..ba70ca34f 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -120,7 +120,9 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st // width = 213.5 - 106.5 = 107 // You'll notice that this is the same width we calculated for the parent view because we've taken its position into account. -- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition +- (void)applyLayoutNode:(css_node_t *)node + viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame + absolutePosition:(CGPoint)absolutePosition { if (!node->layout.should_update) { return; @@ -161,12 +163,19 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st for (int i = 0; i < node->children_count; ++i) { RCTShadowView *child = (RCTShadowView *)_reactSubviews[i]; - [child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; + [child applyLayoutNode:node->get_child(node->context, i) + viewsWithNewFrame:viewsWithNewFrame + absolutePosition:absolutePosition]; } } -- (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties +- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties { + // TODO: we always refresh all propagated properties when propagation is + // dirtied, but really we should track which properties have changed and + // only update those. + if (!_backgroundColor) { UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp]; if (parentBackgroundColor) { @@ -190,14 +199,15 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st return parentProperties; } -- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties +- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties { if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) { return; } _propagationLifecycle = RCTUpdateLifecycleComputed; _lastParentProperties = parentProperties; - NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties]; + NSDictionary *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties]; for (RCTShadowView *child in _reactSubviews) { [child collectUpdatedProperties:applierBlocks parentProperties:nextProps]; } @@ -212,21 +222,19 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st - (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor { - CGFloat totalOffsetTop = 0.0; - CGFloat totalOffsetLeft = 0.0; - CGSize size = self.frame.size; + CGPoint offset = CGPointZero; NSInteger depth = 30; // max depth to search RCTShadowView *shadowView = self; while (depth && shadowView && shadowView != ancestor) { - totalOffsetTop += shadowView.frame.origin.y; - totalOffsetLeft += shadowView.frame.origin.x; + offset.x += shadowView.frame.origin.x; + offset.y += shadowView.frame.origin.y; shadowView = shadowView->_superview; depth--; } if (ancestor != shadowView) { return CGRectNull; } - return (CGRect){{totalOffsetLeft, totalOffsetTop}, size}; + return (CGRect){offset, self.frame.size}; } - (instancetype)init From 57ce9fb11a352dfaa6f08145cf9159fec0224bdb Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 1 Jun 2015 09:27:30 -0700 Subject: [PATCH 03/30] [package.json] Leave the protocol choice for a Github repo to npm Summary: Fixes #1371 Closes https://github.com/facebook/react-native/pull/1447 Github Author: Brent Vatne Test Plan: Imported from GitHub, without a `Test Plan:` line. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 535d59a57..19226bc08 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "react-timer-mixin": "^0.13.1", "react-tools": "0.13.2", "rebound": "^0.0.12", - "sane": "git://github.com/tadeuzagallo/sane.git#a029f8b04a", + "sane": "tadeuzagallo/sane#a029f8b04a", "source-map": "0.1.31", - "stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638", + "stacktrace-parser": "frantic/stacktrace-parser#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", "worker-farm": "^1.3.1", From 2b4daf228d2f99bb36fbbc7458b7e53f462f8d82 Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Mon, 1 Jun 2015 10:19:25 -0700 Subject: [PATCH 04/30] Expose fontScale to JS --- Libraries/Utilities/Dimensions.js | 1 + Libraries/Utilities/PixelRatio.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js index b93000a33..31fa0d4dc 100644 --- a/Libraries/Utilities/Dimensions.js +++ b/Libraries/Utilities/Dimensions.js @@ -29,6 +29,7 @@ if (dimensions && dimensions.windowPhysicalPixels) { width: windowPhysicalPixels.width / windowPhysicalPixels.scale, height: windowPhysicalPixels.height / windowPhysicalPixels.scale, scale: windowPhysicalPixels.scale, + fontScale: windowPhysicalPixels.fontScale, }; // delete so no callers rely on this existing diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js index a3e4d9e77..7660fad30 100644 --- a/Libraries/Utilities/PixelRatio.js +++ b/Libraries/Utilities/PixelRatio.js @@ -59,6 +59,20 @@ class PixelRatio { return Dimensions.get('window').scale; } + /** + * Returns the scaling factor for font sizes. This is the ratio that is used to calculate the + * absolute font size, so any elements that heavily depend on that should use this to do + * calculations. + * + * If a font scale is not set, this returns the device pixel ratio. + * + * Currently this is only implemented on Android and reflects the user preference set in + * Settings > Display > Font size, on iOS it will always return the default pixel ratio. + */ + static getFontScale(): number { + return Dimensions.get('window').fontScale || PixelRatio.get(); + } + /** * Converts a layout size (dp) to pixel size (px). * From d548c85da6ce7cdefe650ff2ef0464652fe8e93a Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 1 Jun 2015 09:47:41 -0700 Subject: [PATCH 05/30] [Bridge] Add support for JS async functions to RCT_EXPORT_METHOD Summary: Adds support for JS async methods and helps guide people writing native modules w.r.t. the callbacks. With this diff, on the native side you write: ```objc RCT_EXPORT_METHOD(getValueAsync:(NSString *)key resolver:(RCTPromiseResolver)resolve rejecter:(RCTPromiseRejecter)reject) { NSError *error = nil; id value = [_nativeDataStore valueForKey:key error:&error]; // "resolve" and "reject" are automatically defined blocks that take // any object (nil is OK) and an NSError, respectively if (!error) { resolve(value); } else { reject(error); } } ``` On the JS side, you can write: ```js var {DemoDataStore} = require('react-native').NativeModules; DemoDataStore.getValueAsync('sample-key').then((value) => { console.log('Got:', value); }, (error) => { console.error(error); // "error" is an Error object whose message is the NSError's description. // The NSError's code and domain are also set, and the native trace i Closes https://github.com/facebook/react-native/pull/1232 Github Author: James Ide Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .../BatchedBridgeFactory.js | 68 ++++++++++---- Libraries/Utilities/MessageQueue.js | 14 +-- React/Base/RCTBridge.m | 89 ++++++++++++++++++- React/Base/RCTBridgeModule.h | 40 ++++++++- 4 files changed, 181 insertions(+), 30 deletions(-) diff --git a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js index dfc09ba7c..4702e246d 100644 --- a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js +++ b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js @@ -19,9 +19,17 @@ var slice = Array.prototype.slice; var MethodTypes = keyMirror({ remote: null, + remoteAsync: null, local: null, }); +type ErrorData = { + message: string; + domain: string; + code: number; + nativeStackIOS?: string; +}; + /** * Creates remotely invokable modules. */ @@ -36,21 +44,40 @@ var BatchedBridgeFactory = { */ _createBridgedModule: function(messageQueue, moduleConfig, moduleName) { var remoteModule = mapObject(moduleConfig.methods, function(methodConfig, memberName) { - return methodConfig.type === MethodTypes.local ? null : function() { - var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; - var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; - var hasSuccCB = typeof lastArg === 'function'; - var hasErrorCB = typeof secondLastArg === 'function'; - hasErrorCB && invariant( - hasSuccCB, - 'Cannot have a non-function arg after a function arg.' - ); - var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); - var args = slice.call(arguments, 0, arguments.length - numCBs); - var onSucc = hasSuccCB ? lastArg : null; - var onFail = hasErrorCB ? secondLastArg : null; - return messageQueue.call(moduleName, memberName, args, onFail, onSucc); - }; + switch (methodConfig.type) { + case MethodTypes.remote: + return function() { + var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; + var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; + var hasErrorCB = typeof lastArg === 'function'; + var hasSuccCB = typeof secondLastArg === 'function'; + hasSuccCB && invariant( + hasErrorCB, + 'Cannot have a non-function arg after a function arg.' + ); + var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); + var args = slice.call(arguments, 0, arguments.length - numCBs); + var onSucc = hasSuccCB ? secondLastArg : null; + var onFail = hasErrorCB ? lastArg : null; + messageQueue.call(moduleName, memberName, args, onSucc, onFail); + }; + + case MethodTypes.remoteAsync: + return function(...args) { + return new Promise((resolve, reject) => { + messageQueue.call(moduleName, memberName, args, resolve, (errorData) => { + var error = _createErrorFromErrorData(errorData); + reject(error); + }); + }); + }; + + case MethodTypes.local: + return null; + + default: + throw new Error('Unknown bridge method type: ' + methodConfig.type); + } }); for (var constName in moduleConfig.constants) { warning(!remoteModule[constName], 'saw constant and method named %s', constName); @@ -59,7 +86,6 @@ var BatchedBridgeFactory = { return remoteModule; }, - create: function(MessageQueue, modulesConfig, localModulesConfig) { var messageQueue = new MessageQueue(modulesConfig, localModulesConfig); return { @@ -80,4 +106,14 @@ var BatchedBridgeFactory = { } }; +function _createErrorFromErrorData(errorData: ErrorData): Error { + var { + message, + ...extraErrorInfo, + } = errorData; + var error = new Error(message); + error.framesToPop = 1; + return Object.assign(error, extraErrorInfo); +} + module.exports = BatchedBridgeFactory; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index df34dde06..5b1989c28 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -431,14 +431,14 @@ var MessageQueueMixin = { }, /** - * @param {Function} onFail Function to store in current thread for later - * lookup, when request fails. * @param {Function} onSucc Function to store in current thread for later * lookup, when request succeeds. + * @param {Function} onFail Function to store in current thread for later + * lookup, when request fails. * @param {Object?=} scope Scope to invoke `cb` with. * @param {Object?=} res Resulting callback ids. Use `this._POOLED_CBIDS`. */ - _storeCallbacksInCurrentThread: function(onFail, onSucc, scope) { + _storeCallbacksInCurrentThread: function(onSucc, onFail, scope) { invariant(onFail || onSucc, INTERNAL_ERROR); this._bookkeeping.allocateCallbackIDs(this._POOLED_CBIDS); var succCBID = this._POOLED_CBIDS.successCallbackID; @@ -494,7 +494,7 @@ var MessageQueueMixin = { return ret; }, - call: function(moduleName, methodName, params, onFail, onSucc, scope) { + call: function(moduleName, methodName, params, onSucc, onFail, scope) { invariant( (!onFail || typeof onFail === 'function') && (!onSucc || typeof onSucc === 'function'), @@ -502,10 +502,10 @@ var MessageQueueMixin = { ); // Store callback _before_ sending the request, just in case the MailBox // returns the response in a blocking manner. - if (onSucc) { - this._storeCallbacksInCurrentThread(onFail, onSucc, scope, this._POOLED_CBIDS); + if (onSucc || onFail) { + this._storeCallbacksInCurrentThread(onSucc, onFail, scope, this._POOLED_CBIDS); + onSucc && params.push(this._POOLED_CBIDS.successCallbackID); onFail && params.push(this._POOLED_CBIDS.errorCallbackID); - params.push(this._POOLED_CBIDS.successCallbackID); } var moduleID = this._remoteModuleNameToModuleID[moduleName]; if (moduleID === undefined || moduleID === null) { diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 47c7f5942..c1028a3fe 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -47,6 +47,11 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldFlushDateMillis }; +typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) { + RCTJavaScriptFunctionKindNormal, + RCTJavaScriptFunctionKindAsync, +}; + #ifdef __LP64__ typedef uint64_t RCTHeaderValue; typedef struct section_64 RCTHeaderSection; @@ -204,6 +209,27 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) return RCTModuleClassesByID; } +// TODO: Can we just replace RCTMakeError with this function instead? +static NSDictionary *RCTJSErrorFromNSError(NSError *error) +{ + NSString *errorMessage; + NSArray *stackTrace = [NSThread callStackSymbols]; + NSMutableDictionary *errorInfo = + [NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"]; + + if (error) { + errorMessage = error.localizedDescription ?: @"Unknown error from a native module"; + errorInfo[@"domain"] = error.domain ?: RCTErrorDomain; + errorInfo[@"code"] = @(error.code); + } else { + errorMessage = @"Unknown error from a native module"; + errorInfo[@"domain"] = RCTErrorDomain; + errorInfo[@"code"] = @-1; + } + + return RCTMakeError(errorMessage, nil, errorInfo); +} + @class RCTBatchedBridge; @interface RCTBridge () @@ -239,6 +265,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) @property (nonatomic, copy, readonly) NSString *moduleClassName; @property (nonatomic, copy, readonly) NSString *JSMethodName; @property (nonatomic, assign, readonly) SEL selector; +@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind; @end @@ -420,6 +447,50 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { addBlockArgument(); useFallback = NO; + } else if ([argumentName isEqualToString:@"RCTPromiseResolveBlock"]) { + RCTAssert(i == numberOfArguments - 2, + @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]", + _moduleClassName, objCMethodName); + RCT_ARG_BLOCK( + if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index, + json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); + return; + } + + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing RCTPromiseResolveBlock value = (^(id result) { + NSArray *arguments = result ? @[result] : @[]; + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, arguments] + context:context]; + }); + ) + useFallback = NO; + _functionKind = RCTJavaScriptFunctionKindAsync; + } else if ([argumentName isEqualToString:@"RCTPromiseRejectBlock"]) { + RCTAssert(i == numberOfArguments - 1, + @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]", + _moduleClassName, objCMethodName); + RCT_ARG_BLOCK( + if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { + RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index, + json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); + return; + } + + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) { + NSDictionary *errorJSON = RCTJSErrorFromNSError(error); + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, @[errorJSON]] + context:context]; + }); + ) + useFallback = NO; + _functionKind = RCTJavaScriptFunctionKindAsync; } } @@ -498,9 +569,18 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) // Safety check if (arguments.count != _argumentBlocks.count) { + NSInteger actualCount = arguments.count; + NSInteger expectedCount = _argumentBlocks.count; + + // Subtract the implicit Promise resolver and rejecter functions for implementations of async functions + if (_functionKind == RCTJavaScriptFunctionKindAsync) { + actualCount -= 2; + expectedCount -= 2; + } + RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd", RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, - arguments.count, _argumentBlocks.count); + actualCount, expectedCount); return; } } @@ -525,7 +605,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName]; + return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", + NSStringFromClass(self.class), self, _methodName, _JSMethodName]; } @end @@ -606,7 +687,7 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) * }, * "methodName2": { * "methodID": 1, - * "type": "remote" + * "type": "remoteAsync" * }, * etc... * }, @@ -630,7 +711,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) [methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) { methodsByName[method.JSMethodName] = @{ @"methodID": @(methodID), - @"type": @"remote", + @"type": method.functionKind == RCTJavaScriptFunctionKindAsync ? @"remoteAsync" : @"remote", }; }]; diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 34b861ff3..1528b8cd9 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -17,6 +17,20 @@ */ typedef void (^RCTResponseSenderBlock)(NSArray *response); +/** + * Block that bridge modules use to resolve the JS promise waiting for a result. + * Nil results are supported and are converted to JS's undefined value. + */ +typedef void (^RCTPromiseResolveBlock)(id result); + +/** + * Block that bridge modules use to reject the JS promise waiting for a result. + * The error may be nil but it is preferable to pass an NSError object for more + * precise error messages. + */ +typedef void (^RCTPromiseRejectBlock)(NSError *error); + + /** * This constant can be returned from +methodQueue to force module * methods to be called on the JavaScript thread. This can have serious @@ -37,7 +51,7 @@ extern const dispatch_queue_t RCTJSThread; * A reference to the RCTBridge. Useful for modules that require access * to bridge features, such as sending events or making JS calls. This * will be set automatically by the bridge when it initializes the module. -* To implement this in your module, just add @synthesize bridge = _bridge; + * To implement this in your module, just add @synthesize bridge = _bridge; */ @property (nonatomic, weak) RCTBridge *bridge; @@ -70,6 +84,26 @@ extern const dispatch_queue_t RCTJSThread; * { ... } * * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`. + * + * ## Promises + * + * Bridge modules can also define methods that are exported to JavaScript as + * methods that return a Promise, and are compatible with JS async functions. + * + * Declare the last two parameters of your native method to be a resolver block + * and a rejecter block. The resolver block must precede the rejecter block. + * + * For example: + * + * RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString + * resolver:(RCTPromiseResolveBlock)resolve + * rejecter:(RCTPromiseRejectBlock)reject + * { ... } + * + * Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from + * JavaScript will return a promise that is resolved or rejected when your + * native method implementation calls the respective block. + * */ #define RCT_EXPORT_METHOD(method) \ RCT_REMAP_METHOD(, method) @@ -118,7 +152,7 @@ extern const dispatch_queue_t RCTJSThread; RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername) /** - * Similar to RCT_EXTERN_MODULE but allows setting a custom JavaScript name + * Like RCT_EXTERN_MODULE, but allows setting a custom JavaScript name. */ #define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \ objc_name : objc_supername \ @@ -136,7 +170,7 @@ extern const dispatch_queue_t RCTJSThread; RCT_EXTERN_REMAP_METHOD(, method) /** - * Similar to RCT_EXTERN_REMAP_METHOD but allows setting a custom JavaScript name + * Like RCT_EXTERN_REMAP_METHOD, but allows setting a custom JavaScript name. */ #define RCT_EXTERN_REMAP_METHOD(js_name, method) \ - (void)__rct_export__##method { \ From d723e176292c4398250d6ee0da64b4ce0d223d4d Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 1 Jun 2015 12:09:52 -0700 Subject: [PATCH 06/30] [ReactNative] Copy assets to corresponding folders on Android --- Libraries/Image/__tests__/resolveAssetSource-test.js | 2 +- Libraries/Image/resolveAssetSource.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index 866cf0368..5854dae0b 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -175,7 +175,7 @@ describe('resolveAssetSource', () => { isStatic: true, width: 100, height: 200, - uri: 'assets_awesomemodule_subdir_logo1_', + uri: 'awesomemodule_subdir_logo1_', }); }); }); diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 26592195d..301d70dd9 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -50,7 +50,8 @@ function getPathInArchive(asset) { return (assetDir + '/' + asset.name) .toLowerCase() .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, ''); // Remove illegal chars + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^assets_/, ''); // Remove "assets_" prefix } else { // E.g. 'assets/AwesomeModule/icon@2x.png' return getScaledAssetPath(asset); From 34cef28a10b7d44be453c072578b5b6c07a0dcd8 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 1 Jun 2015 13:27:21 -0700 Subject: [PATCH 07/30] [ReactNative] kill setInterval in ListView --- Libraries/CustomComponents/ListView/ListView.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 17f1e477d..717f03847 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -45,7 +45,6 @@ var DEFAULT_INITIAL_ROWS = 10; var DEFAULT_SCROLL_RENDER_AHEAD = 1000; var DEFAULT_END_REACHED_THRESHOLD = 1000; var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; -var RENDER_INTERVAL = 20; var SCROLLVIEW_REF = 'listviewscroll'; @@ -258,7 +257,6 @@ var ListView = React.createClass({ // the component is laid out this.requestAnimationFrame(() => { this._measureAndUpdateScrollProps(); - this.setInterval(this._renderMoreRowsIfNeeded, RENDER_INTERVAL); }); }, @@ -329,7 +327,7 @@ var ListView = React.createClass({ totalIndex++; if (this.props.renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length -1)) { + (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) { var adjacentRowHighlighted = this.state.highlightedRow.sectionID === sectionID && ( this.state.highlightedRow.rowID === rowID || @@ -397,6 +395,7 @@ var ListView = React.createClass({ _setScrollVisibleHeight: function(left, top, width, height) { this.scrollProperties.visibleHeight = height; this._updateVisibleRows(); + this._renderMoreRowsIfNeeded(); }, _renderMoreRowsIfNeeded: function() { @@ -443,8 +442,8 @@ var ListView = React.createClass({ } var updatedFrames = e && e.nativeEvent.updatedChildFrames; if (updatedFrames) { - updatedFrames.forEach((frame) => { - this._childFrames[frame.index] = merge(frame); + updatedFrames.forEach((newFrame) => { + this._childFrames[newFrame.index] = merge(newFrame); }); } var dataSource = this.props.dataSource; From 43adb7b02c49c3a05c3d4f884c1165303556e372 Mon Sep 17 00:00:00 2001 From: Joshua Sierles Date: Mon, 1 Jun 2015 15:46:06 -0700 Subject: [PATCH 08/30] [CameraRoll] support fetching videos from the camera roll Summary: This adds a parameter for fetching videos from the camera roll. It also changes the default to fetch both videos and photos. Closes https://github.com/facebook/react-native/pull/774 Github Author: Joshua Sierles Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/CameraRollView.ios.js | 15 ++++++++++++++- Libraries/CameraRoll/CameraRoll.js | 15 +++++++++++++++ Libraries/Image/RCTCameraRollManager.m | 12 +++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Examples/UIExplorer/CameraRollView.ios.js b/Examples/UIExplorer/CameraRollView.ios.js index 87dd23e6d..74507aa2c 100644 --- a/Examples/UIExplorer/CameraRollView.ios.js +++ b/Examples/UIExplorer/CameraRollView.ios.js @@ -59,6 +59,16 @@ var propTypes = { * imagesPerRow: Number of images to be shown in each row. */ imagesPerRow: React.PropTypes.number, + + /** + * The asset type, one of 'Photos', 'Videos' or 'All' + */ + assetType: React.PropTypes.oneOf([ + 'Photos', + 'Videos', + 'All', + ]), + }; var CameraRollView = React.createClass({ @@ -69,6 +79,7 @@ var CameraRollView = React.createClass({ groupTypes: 'SavedPhotos', batchSize: 5, imagesPerRow: 1, + assetType: 'Photos', renderImage: function(asset) { var imageSize = 150; var imageStyle = [styles.image, {width: imageSize, height: imageSize}]; @@ -89,6 +100,7 @@ var CameraRollView = React.createClass({ assets: ([]: Array), groupTypes: this.props.groupTypes, lastCursor: (null : ?string), + assetType: this.props.assetType, noMore: false, loadingMore: false, dataSource: ds, @@ -124,7 +136,8 @@ var CameraRollView = React.createClass({ var fetchParams: Object = { first: this.props.batchSize, - groupTypes: this.props.groupTypes + groupTypes: this.props.groupTypes, + assetType: this.props.assetType, }; if (this.state.lastCursor) { fetchParams.after = this.state.lastCursor; diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 0d008ae75..67fa50830 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -29,8 +29,16 @@ var GROUP_TYPES_OPTIONS = [ 'SavedPhotos', // default ]; +var ASSET_TYPE_OPTIONS = [ + 'All', + 'Videos', + 'Photos', // default +]; + + // Flow treats Object and Array as disjoint types, currently. deepFreezeAndThrowOnMutationInDev((GROUP_TYPES_OPTIONS: any)); +deepFreezeAndThrowOnMutationInDev((ASSET_TYPE_OPTIONS: any)); /** * Shape of the param arg for the `getPhotos` function. @@ -58,6 +66,11 @@ var getPhotosParamChecker = createStrictShapeTypeChecker({ * titles. */ groupName: ReactPropTypes.string, + + /** + * Specifies filter on asset type + */ + assetType: ReactPropTypes.oneOf(ASSET_TYPE_OPTIONS), }); /** @@ -94,6 +107,7 @@ var getPhotosReturnChecker = createStrictShapeTypeChecker({ class CameraRoll { static GroupTypesOptions: Array; + static AssetTypeOptions: Array; /** * Saves the image with tag `tag` to the camera roll. * @@ -154,5 +168,6 @@ class CameraRoll { } CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS; +CameraRoll.AssetTypeOptions = ASSET_TYPE_OPTIONS; module.exports = CameraRoll; diff --git a/Libraries/Image/RCTCameraRollManager.m b/Libraries/Image/RCTCameraRollManager.m index 8e6c8a532..d7b42f885 100644 --- a/Libraries/Image/RCTCameraRollManager.m +++ b/Libraries/Image/RCTCameraRollManager.m @@ -69,7 +69,9 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params NSString *afterCursor = params[@"after"]; NSString *groupTypesStr = params[@"groupTypes"]; NSString *groupName = params[@"groupName"]; + NSString *assetType = params[@"assetType"]; ALAssetsGroupType groupTypes; + if ([groupTypesStr isEqualToString:@"Album"]) { groupTypes = ALAssetsGroupAlbum; } else if ([groupTypesStr isEqualToString:@"All"]) { @@ -93,7 +95,15 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params [[RCTImageLoader assetsLibrary] enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) { if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) { - [group setAssetsFilter:ALAssetsFilter.allPhotos]; + + if (assetType == nil || [assetType isEqualToString:@"Photos"]) { + [group setAssetsFilter:ALAssetsFilter.allPhotos]; + } else if ([assetType isEqualToString:@"Videos"]) { + [group setAssetsFilter:ALAssetsFilter.allVideos]; + } else if ([assetType isEqualToString:@"All"]) { + [group setAssetsFilter:ALAssetsFilter.allAssets]; + } + [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) { if (result) { NSString *uri = [(NSURL *)[result valueForProperty:ALAssetPropertyAssetURL] absoluteString]; From 38f57ee18c44f68c1a1745ed81394481e1275ae9 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 1 Jun 2015 16:04:16 -0700 Subject: [PATCH 09/30] [ReactNative] improve UIExplorer keyboard interactions --- Examples/UIExplorer/UIExplorerList.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index fdbda4dc8..a030220ca 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -30,7 +30,7 @@ var { var { TestModule } = React.addons; var Settings = require('Settings'); -import type { Example, ExampleModule } from 'ExampleTypes'; +import type { ExampleModule } from 'ExampleTypes'; var createExamplePage = require('./createExamplePage'); @@ -154,7 +154,9 @@ class UIExplorerList extends React.Component { dataSource={this.state.dataSource} renderRow={this._renderRow.bind(this)} renderSectionHeader={this._renderSectionHeader} + keyboardShouldPersistTaps={true} automaticallyAdjustContentInsets={false} + keyboardDismissMode="onDrag" /> ); From 2a6fe079c08231389b56370a027f851258578770 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 1 Jun 2015 15:58:22 -0700 Subject: [PATCH 10/30] [Timers] Batch setImmediate handlers Summary: Wraps the setImmediate handlers in a `batchUpdates` call before they are synchronously executed at the end of the JS execution loop. Closes https://github.com/facebook/react-native/pull/1242 Github Author: James Ide Test Plan: Added two `setImmediate` calls to `componentDidMount` in UIExplorerApp. Each handler calls `setState`, and `componentWillUpdate` logs its state. With this diff, we can see the state updates are successfully batched. ```javascript componentDidMount() { setImmediate(() => { console.log('immediate 1'); this.setState({a: 1}); }); setImmediate(() => { console.log('immediate 2'); this.setState({a: 2}); }); }, componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate with next state.a =', nextState.a); }, ``` **Before:** "immediate 1" "componentWillUpdate with next state.a =", 1 "immediate 2" "componentWillUpdate with next state.a =", 2 **After:** "immediate 1" "immediate 2" "componentWillUpdate with next state.a =", 2 Addresses the batching issue in #1232. cc @vjeux @spicyj --- Libraries/Utilities/MessageQueue.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 5b1989c28..2ff8b81d7 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -472,8 +472,10 @@ var MessageQueueMixin = { }, _flushedQueueUnguarded: function() { - // Call the functions registred via setImmediate - JSTimersExecution.callImmediates(); + ReactUpdates.batchedUpdates(() => { + // Call the functions registered via setImmediate + JSTimersExecution.callImmediates(); + }); var currentOutgoingItems = this._outgoingItems; this._swapAndReinitializeBuffer(); From 219a7c1bfdf12bcd1ea38b74fc692a89069d14f1 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Mon, 1 Jun 2015 16:29:52 -0700 Subject: [PATCH 11/30] [ReactNative] Navigator block touches on scene when navigating --- Libraries/CustomComponents/Navigator/Navigator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index f75430b77..56bd98c73 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -1290,7 +1290,7 @@ var Navigator = React.createClass({ key={this.state.idStack[i]} ref={'scene_' + i} onStartShouldSetResponderCapture={() => { - return i !== this.state.presentedIndex; + return !!this.state.transitionFromIndex || !!this.state.activeGesture; }} style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}> {React.cloneElement(child, { From e6c04df5a11628500a6538a546b3c549fda0cf99 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Mon, 1 Jun 2015 17:30:49 -0700 Subject: [PATCH 12/30] fix bug with inspector clicking Summary: Previously, if you were already inspecting an element, touching again would select a completely different element because the touch position was calculated relative to the current overlay. This fixes it. @public Test Plan: Open the inspector, click around, verify that every click selects the thing you clicked on. --- Libraries/ReactIOS/InspectorOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay.js index 8b5c6c0cb..eeb6e7965 100644 --- a/Libraries/ReactIOS/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay.js @@ -59,7 +59,7 @@ var InspectorOverlay = React.createClass({ ? 'flex-start' : 'flex-end'; - content.push(); + content.push(); content.push(); } return ( From 1ed2542b464270f6da28f2255471b80ec36682a5 Mon Sep 17 00:00:00 2001 From: Chace Liang Date: Mon, 1 Jun 2015 20:17:25 -0700 Subject: [PATCH 13/30] Revert "[Bridge] Add support for JS async functions to RCT_EXPORT_METHOD" --- .../BatchedBridgeFactory.js | 68 ++++---------- Libraries/Utilities/MessageQueue.js | 14 +-- React/Base/RCTBridge.m | 89 +------------------ React/Base/RCTBridgeModule.h | 40 +-------- 4 files changed, 30 insertions(+), 181 deletions(-) diff --git a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js index 4702e246d..dfc09ba7c 100644 --- a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js +++ b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js @@ -19,17 +19,9 @@ var slice = Array.prototype.slice; var MethodTypes = keyMirror({ remote: null, - remoteAsync: null, local: null, }); -type ErrorData = { - message: string; - domain: string; - code: number; - nativeStackIOS?: string; -}; - /** * Creates remotely invokable modules. */ @@ -44,40 +36,21 @@ var BatchedBridgeFactory = { */ _createBridgedModule: function(messageQueue, moduleConfig, moduleName) { var remoteModule = mapObject(moduleConfig.methods, function(methodConfig, memberName) { - switch (methodConfig.type) { - case MethodTypes.remote: - return function() { - var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; - var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; - var hasErrorCB = typeof lastArg === 'function'; - var hasSuccCB = typeof secondLastArg === 'function'; - hasSuccCB && invariant( - hasErrorCB, - 'Cannot have a non-function arg after a function arg.' - ); - var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); - var args = slice.call(arguments, 0, arguments.length - numCBs); - var onSucc = hasSuccCB ? secondLastArg : null; - var onFail = hasErrorCB ? lastArg : null; - messageQueue.call(moduleName, memberName, args, onSucc, onFail); - }; - - case MethodTypes.remoteAsync: - return function(...args) { - return new Promise((resolve, reject) => { - messageQueue.call(moduleName, memberName, args, resolve, (errorData) => { - var error = _createErrorFromErrorData(errorData); - reject(error); - }); - }); - }; - - case MethodTypes.local: - return null; - - default: - throw new Error('Unknown bridge method type: ' + methodConfig.type); - } + return methodConfig.type === MethodTypes.local ? null : function() { + var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; + var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; + var hasSuccCB = typeof lastArg === 'function'; + var hasErrorCB = typeof secondLastArg === 'function'; + hasErrorCB && invariant( + hasSuccCB, + 'Cannot have a non-function arg after a function arg.' + ); + var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); + var args = slice.call(arguments, 0, arguments.length - numCBs); + var onSucc = hasSuccCB ? lastArg : null; + var onFail = hasErrorCB ? secondLastArg : null; + return messageQueue.call(moduleName, memberName, args, onFail, onSucc); + }; }); for (var constName in moduleConfig.constants) { warning(!remoteModule[constName], 'saw constant and method named %s', constName); @@ -86,6 +59,7 @@ var BatchedBridgeFactory = { return remoteModule; }, + create: function(MessageQueue, modulesConfig, localModulesConfig) { var messageQueue = new MessageQueue(modulesConfig, localModulesConfig); return { @@ -106,14 +80,4 @@ var BatchedBridgeFactory = { } }; -function _createErrorFromErrorData(errorData: ErrorData): Error { - var { - message, - ...extraErrorInfo, - } = errorData; - var error = new Error(message); - error.framesToPop = 1; - return Object.assign(error, extraErrorInfo); -} - module.exports = BatchedBridgeFactory; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 2ff8b81d7..9f07cf4c0 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -431,14 +431,14 @@ var MessageQueueMixin = { }, /** - * @param {Function} onSucc Function to store in current thread for later - * lookup, when request succeeds. * @param {Function} onFail Function to store in current thread for later * lookup, when request fails. + * @param {Function} onSucc Function to store in current thread for later + * lookup, when request succeeds. * @param {Object?=} scope Scope to invoke `cb` with. * @param {Object?=} res Resulting callback ids. Use `this._POOLED_CBIDS`. */ - _storeCallbacksInCurrentThread: function(onSucc, onFail, scope) { + _storeCallbacksInCurrentThread: function(onFail, onSucc, scope) { invariant(onFail || onSucc, INTERNAL_ERROR); this._bookkeeping.allocateCallbackIDs(this._POOLED_CBIDS); var succCBID = this._POOLED_CBIDS.successCallbackID; @@ -496,7 +496,7 @@ var MessageQueueMixin = { return ret; }, - call: function(moduleName, methodName, params, onSucc, onFail, scope) { + call: function(moduleName, methodName, params, onFail, onSucc, scope) { invariant( (!onFail || typeof onFail === 'function') && (!onSucc || typeof onSucc === 'function'), @@ -504,10 +504,10 @@ var MessageQueueMixin = { ); // Store callback _before_ sending the request, just in case the MailBox // returns the response in a blocking manner. - if (onSucc || onFail) { - this._storeCallbacksInCurrentThread(onSucc, onFail, scope, this._POOLED_CBIDS); - onSucc && params.push(this._POOLED_CBIDS.successCallbackID); + if (onSucc) { + this._storeCallbacksInCurrentThread(onFail, onSucc, scope, this._POOLED_CBIDS); onFail && params.push(this._POOLED_CBIDS.errorCallbackID); + params.push(this._POOLED_CBIDS.successCallbackID); } var moduleID = this._remoteModuleNameToModuleID[moduleName]; if (moduleID === undefined || moduleID === null) { diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index c1028a3fe..47c7f5942 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -47,11 +47,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldFlushDateMillis }; -typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) { - RCTJavaScriptFunctionKindNormal, - RCTJavaScriptFunctionKindAsync, -}; - #ifdef __LP64__ typedef uint64_t RCTHeaderValue; typedef struct section_64 RCTHeaderSection; @@ -209,27 +204,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void) return RCTModuleClassesByID; } -// TODO: Can we just replace RCTMakeError with this function instead? -static NSDictionary *RCTJSErrorFromNSError(NSError *error) -{ - NSString *errorMessage; - NSArray *stackTrace = [NSThread callStackSymbols]; - NSMutableDictionary *errorInfo = - [NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"]; - - if (error) { - errorMessage = error.localizedDescription ?: @"Unknown error from a native module"; - errorInfo[@"domain"] = error.domain ?: RCTErrorDomain; - errorInfo[@"code"] = @(error.code); - } else { - errorMessage = @"Unknown error from a native module"; - errorInfo[@"domain"] = RCTErrorDomain; - errorInfo[@"code"] = @-1; - } - - return RCTMakeError(errorMessage, nil, errorInfo); -} - @class RCTBatchedBridge; @interface RCTBridge () @@ -265,7 +239,6 @@ static NSDictionary *RCTJSErrorFromNSError(NSError *error) @property (nonatomic, copy, readonly) NSString *moduleClassName; @property (nonatomic, copy, readonly) NSString *JSMethodName; @property (nonatomic, assign, readonly) SEL selector; -@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind; @end @@ -447,50 +420,6 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) } else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) { addBlockArgument(); useFallback = NO; - } else if ([argumentName isEqualToString:@"RCTPromiseResolveBlock"]) { - RCTAssert(i == numberOfArguments - 2, - @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]", - _moduleClassName, objCMethodName); - RCT_ARG_BLOCK( - if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { - RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index, - json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); - return; - } - - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing RCTPromiseResolveBlock value = (^(id result) { - NSArray *arguments = result ? @[result] : @[]; - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, arguments] - context:context]; - }); - ) - useFallback = NO; - _functionKind = RCTJavaScriptFunctionKindAsync; - } else if ([argumentName isEqualToString:@"RCTPromiseRejectBlock"]) { - RCTAssert(i == numberOfArguments - 1, - @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]", - _moduleClassName, objCMethodName); - RCT_ARG_BLOCK( - if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { - RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index, - json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName); - return; - } - - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) { - NSDictionary *errorJSON = RCTJSErrorFromNSError(error); - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, @[errorJSON]] - context:context]; - }); - ) - useFallback = NO; - _functionKind = RCTJavaScriptFunctionKindAsync; } } @@ -569,18 +498,9 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) // Safety check if (arguments.count != _argumentBlocks.count) { - NSInteger actualCount = arguments.count; - NSInteger expectedCount = _argumentBlocks.count; - - // Subtract the implicit Promise resolver and rejecter functions for implementations of async functions - if (_functionKind == RCTJavaScriptFunctionKindAsync) { - actualCount -= 2; - expectedCount -= 2; - } - RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd", RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, - actualCount, expectedCount); + arguments.count, _argumentBlocks.count); return; } } @@ -605,8 +525,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName) - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", - NSStringFromClass(self.class), self, _methodName, _JSMethodName]; + return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName]; } @end @@ -687,7 +606,7 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) * }, * "methodName2": { * "methodID": 1, - * "type": "remoteAsync" + * "type": "remote" * }, * etc... * }, @@ -711,7 +630,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) [methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) { methodsByName[method.JSMethodName] = @{ @"methodID": @(methodID), - @"type": method.functionKind == RCTJavaScriptFunctionKindAsync ? @"remoteAsync" : @"remote", + @"type": @"remote", }; }]; diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 1528b8cd9..34b861ff3 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -17,20 +17,6 @@ */ typedef void (^RCTResponseSenderBlock)(NSArray *response); -/** - * Block that bridge modules use to resolve the JS promise waiting for a result. - * Nil results are supported and are converted to JS's undefined value. - */ -typedef void (^RCTPromiseResolveBlock)(id result); - -/** - * Block that bridge modules use to reject the JS promise waiting for a result. - * The error may be nil but it is preferable to pass an NSError object for more - * precise error messages. - */ -typedef void (^RCTPromiseRejectBlock)(NSError *error); - - /** * This constant can be returned from +methodQueue to force module * methods to be called on the JavaScript thread. This can have serious @@ -51,7 +37,7 @@ extern const dispatch_queue_t RCTJSThread; * A reference to the RCTBridge. Useful for modules that require access * to bridge features, such as sending events or making JS calls. This * will be set automatically by the bridge when it initializes the module. - * To implement this in your module, just add @synthesize bridge = _bridge; +* To implement this in your module, just add @synthesize bridge = _bridge; */ @property (nonatomic, weak) RCTBridge *bridge; @@ -84,26 +70,6 @@ extern const dispatch_queue_t RCTJSThread; * { ... } * * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`. - * - * ## Promises - * - * Bridge modules can also define methods that are exported to JavaScript as - * methods that return a Promise, and are compatible with JS async functions. - * - * Declare the last two parameters of your native method to be a resolver block - * and a rejecter block. The resolver block must precede the rejecter block. - * - * For example: - * - * RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString - * resolver:(RCTPromiseResolveBlock)resolve - * rejecter:(RCTPromiseRejectBlock)reject - * { ... } - * - * Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from - * JavaScript will return a promise that is resolved or rejected when your - * native method implementation calls the respective block. - * */ #define RCT_EXPORT_METHOD(method) \ RCT_REMAP_METHOD(, method) @@ -152,7 +118,7 @@ extern const dispatch_queue_t RCTJSThread; RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername) /** - * Like RCT_EXTERN_MODULE, but allows setting a custom JavaScript name. + * Similar to RCT_EXTERN_MODULE but allows setting a custom JavaScript name */ #define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \ objc_name : objc_supername \ @@ -170,7 +136,7 @@ extern const dispatch_queue_t RCTJSThread; RCT_EXTERN_REMAP_METHOD(, method) /** - * Like RCT_EXTERN_REMAP_METHOD, but allows setting a custom JavaScript name. + * Similar to RCT_EXTERN_REMAP_METHOD but allows setting a custom JavaScript name */ #define RCT_EXTERN_REMAP_METHOD(js_name, method) \ - (void)__rct_export__##method { \ From 570597c4ac0db0d75a1a51cfc8b07453d4d3e91d Mon Sep 17 00:00:00 2001 From: John Harper Date: Tue, 2 Jun 2015 01:38:50 -0700 Subject: [PATCH 14/30] [react-native] dispatch perf updates to main thread --- React/Base/RCTBridge.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 47c7f5942..f122611cd 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1555,7 +1555,9 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); - [self.perfStats.jsGraph tick:displayLink.timestamp]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.perfStats.jsGraph tick:displayLink.timestamp]; + }); } - (void)_mainThreadUpdate:(CADisplayLink *)displayLink From dc6fd822312207ac3901ffc522891cb2e11c377b Mon Sep 17 00:00:00 2001 From: John Harper Date: Tue, 2 Jun 2015 01:40:43 -0700 Subject: [PATCH 15/30] [react-native] -mountOrUnmountSubview: should handle zero-sized clip-rects correctly --- React/Views/RCTView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index 2cc03d874..a584a5798 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -277,7 +277,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view) // View has cliping enabled, so we can easily test if it is partially // or completely within the clipRect, and mount or unmount it accordingly - if (CGRectIntersectsRect(clipRect, view.frame)) { + if (!CGRectIsEmpty(CGRectIntersection(clipRect, view.frame))) { // View is at least partially visible, so remount it if unmounted if (view.superview == nil) { From 777363fdd72916ed1a977e22de3a5bd13eecc42f Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 2 Jun 2015 02:58:49 -0700 Subject: [PATCH 16/30] [ReactNative] Use NSDictionary + NSNumber for event coalescing Summary: @public On D2099270 event coalescing was implemented and the event key on the RCTSparseArray is an uint64_t, but it was declared as NSUInteger. On a 32 bits architecture it'll be clipped to 4 bits, meaning that just `reactTag` will be taken into account, e.g. different types of events can coalesce with each other if they target the same view Switching to use an NSMutableDictionary instead of RCTSparseArray and NSNumber as keys instead of uint64_t Test Plan: Fixed the previous tests and added a new test to RCTEventDispatcherTests --- React/Base/RCTEventDispatcher.m | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index e6ed698d9..2009df542 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -11,11 +11,10 @@ #import "RCTAssert.h" #import "RCTBridge.h" -#import "RCTSparseArray.h" -static uint64_t RCTGetEventID(id event) +static NSNumber *RCTGetEventID(id event) { - return ( + return @( [event.viewTag intValue] | (((uint64_t)event.eventName.hash & 0xFFFF) << 32) | (((uint64_t)event.coalescingKey) << 48) @@ -68,7 +67,7 @@ static uint64_t RCTGetEventID(id event) @implementation RCTEventDispatcher { - RCTSparseArray *_eventQueue; + NSMutableDictionary *_eventQueue; NSLock *_eventQueueLock; } @@ -79,7 +78,7 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { - _eventQueue = [[RCTSparseArray alloc] init]; + _eventQueue = [[NSMutableDictionary alloc] init]; _eventQueueLock = [[NSLock alloc] init]; } return self; @@ -139,7 +138,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent); [_eventQueueLock lock]; - uint64_t eventID = RCTGetEventID(event); + NSNumber *eventID = RCTGetEventID(event); id previousEvent = _eventQueue[eventID]; if (previousEvent) { @@ -176,14 +175,14 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent); - (void)didUpdateFrame:(RCTFrameUpdate *)update { - RCTSparseArray *eventQueue; + NSDictionary *eventQueue; [_eventQueueLock lock]; eventQueue = _eventQueue; - _eventQueue = [[RCTSparseArray alloc] init]; + _eventQueue = [[NSMutableDictionary alloc] init]; [_eventQueueLock unlock]; - for (id event in eventQueue.allObjects) { + for (id event in eventQueue.allValues) { [self dispatchEvent:event]; } } From 0aa7f3f8d52afbdf07e30c4b371ed6d7b5439a97 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Tue, 2 Jun 2015 05:31:30 -0700 Subject: [PATCH 17/30] [react_native] Implement connectivity module --- Examples/UIExplorer/NetInfoExample.js | 10 +- Libraries/Network/NetInfo.js | 243 +++++++++++++++++--------- 2 files changed, 162 insertions(+), 91 deletions(-) diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js index c322a7432..6ab1805df 100644 --- a/Examples/UIExplorer/NetInfoExample.js +++ b/Examples/UIExplorer/NetInfoExample.js @@ -29,13 +29,13 @@ var ReachabilitySubscription = React.createClass({ }; }, componentDidMount: function() { - NetInfo.reachabilityIOS.addEventListener( + NetInfo.addEventListener( 'change', this._handleReachabilityChange ); }, componentWillUnmount: function() { - NetInfo.reachabilityIOS.removeEventListener( + NetInfo.removeEventListener( 'change', this._handleReachabilityChange ); @@ -63,16 +63,16 @@ var ReachabilityCurrent = React.createClass({ }; }, componentDidMount: function() { - NetInfo.reachabilityIOS.addEventListener( + NetInfo.addEventListener( 'change', this._handleReachabilityChange ); - NetInfo.reachabilityIOS.fetch().done( + NetInfo.fetch().done( (reachability) => { this.setState({reachability}); } ); }, componentWillUnmount: function() { - NetInfo.reachabilityIOS.removeEventListener( + NetInfo.removeEventListener( 'change', this._handleReachabilityChange ); diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index 2b65671a9..47184d181 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -12,8 +12,14 @@ 'use strict'; var NativeModules = require('NativeModules'); +var Platform = require('Platform'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var RCTReachability = NativeModules.Reachability; + +if (Platform.OS === 'ios') { + var RCTNetInfo = NativeModules.Reachability; +} else if (Platform.OS === 'android') { + var RCTNetInfo = NativeModules.NetInfo; +} var DEVICE_REACHABILITY_EVENT = 'reachabilityDidChange'; @@ -28,11 +34,50 @@ type ReachabilityStateIOS = $Enum<{ wifi: string; }>; +type ConnectivityStateAndroid = $Enum<{ + NONE: string; + MOBILE: string; + WIFI: string; + MOBILE_MMS: string; + MOBILE_SUPL: string; + MOBILE_DUN: string; + MOBILE_HIPRI: string; + WIMAX: string; + BLUETOOTH: string; + DUMMY: string; + ETHERNET: string; + MOBILE_FOTA: string; + MOBILE_IMS: string; + MOBILE_CBS: string; + WIFI_P2P: string; + MOBILE_IA: string; + MOBILE_EMERGENCY: string; + PROXY: string; + VPN: string; + UNKNOWN: string; +}>; /** * NetInfo exposes info about online/offline status * - * ### reachabilityIOS + * ``` + * NetInfo.fetch().done((reach) => { + * console.log('Initial: ' + reach); + * }); + * function handleFirstConnectivityChange(reach) { + * console.log('First change: ' + reach); + * NetInfo.removeEventListener( + * 'change', + * handleFirstConnectivityChange + * ); + * } + * NetInfo.addEventListener( + * 'change', + * handleFirstConnectivityChange + * ); + * ``` + * + * ### IOS * * Asynchronously determine if the device is online and on a cellular network. * @@ -41,21 +86,35 @@ type ReachabilityStateIOS = $Enum<{ * - `cell` - device is connected via Edge, 3G, WiMax, or LTE * - `unknown` - error case and the network status is unknown * - * ``` - * NetInfo.reachabilityIOS.fetch().done((reach) => { - * console.log('Initial: ' + reach); + * ### Android + * + * Asynchronously determine if the device is connected and details about that connection. + * + * Android Connectivity Types + * - `NONE` - device is offline + * - `BLUETOOTH` - The Bluetooth data connection. + * - `DUMMY` - Dummy data connection. + * - `ETHERNET` - The Ethernet data connection. + * - `MOBILE` - The Mobile data connection. + * - `MOBILE_DUN` - A DUN-specific Mobile data connection. + * - `MOBILE_HIPRI` - A High Priority Mobile data connection. + * - `MOBILE_MMS` - An MMS-specific Mobile data connection. + * - `MOBILE_SUPL` - A SUPL-specific Mobile data connection. + * - `VPN` - A virtual network using one or more native bearers. Requires API Level 21 + * - `WIFI` - The WIFI data connection. + * - `WIMAX` - The WiMAX data connection. + * - `UNKNOWN` - Unknown data connection. + * The rest ConnectivityStates are hidden by the Android API, but can be used if necessary. + * + * ### isConnectionMetered + * + * Available on Android. Detect if the current active connection is metered or not. A network is + * classified as metered when the user is sensitive to heavy data usage on that connection due to + * monetary costs, data limitations or battery/performance issues. + * + * NetInfo.isConnectionMetered((isConnectionMetered) => { + * console.log('Connection is ' + (isConnectionMetered ? 'Metered' : 'Not Metered')); * }); - * function handleFirstReachabilityChange(reach) { - * console.log('First change: ' + reach); - * NetInfo.reachabilityIOS.removeEventListener( - * 'change', - * handleFirstReachabilityChange - * ); - * } - * NetInfo.reachabilityIOS.addEventListener( - * 'change', - * handleFirstReachabilityChange - * ); * ``` * * ### isConnected @@ -81,89 +140,101 @@ type ReachabilityStateIOS = $Enum<{ * ``` */ -var NetInfo = {}; +var _subscriptions = {}; -if (RCTReachability) { - - // RCTReachability is exposed, so this is an iOS-like environment and we will - // expose reachabilityIOS - - var _reachabilitySubscriptions = {}; - - NetInfo.reachabilityIOS = { - addEventListener: function ( - eventName: ChangeEventName, - handler: Function - ): void { - _reachabilitySubscriptions[handler] = RCTDeviceEventEmitter.addListener( - DEVICE_REACHABILITY_EVENT, - (appStateData) => { - handler(appStateData.network_reachability); - } - ); - }, - - removeEventListener: function( - eventName: ChangeEventName, - handler: Function - ): void { - if (!_reachabilitySubscriptions[handler]) { - return; +var NetInfo = { + addEventListener: function ( + eventName: ChangeEventName, + handler: Function + ): void { + _subscriptions[handler] = RCTDeviceEventEmitter.addListener( + DEVICE_REACHABILITY_EVENT, + (appStateData) => { + handler(appStateData.network_reachability); } - _reachabilitySubscriptions[handler].remove(); - _reachabilitySubscriptions[handler] = null; - }, + ); + }, - fetch: function(): Promise { - return new Promise((resolve, reject) => { - RCTReachability.getCurrentReachability( - function(resp) { - resolve(resp.network_reachability); - }, - reject - ); - }); - }, - }; + removeEventListener: function( + eventName: ChangeEventName, + handler: Function + ): void { + if (!_subscriptions[handler]) { + return; + } + _subscriptions[handler].remove(); + _subscriptions[handler] = null; + }, - var _isConnectedSubscriptions = {}; + fetch: function(): Promise { + return new Promise((resolve, reject) => { + RCTNetInfo.getCurrentReachability( + function(resp) { + resolve(resp.network_reachability); + }, + reject + ); + }); + }, - var _iosReachabilityIsConnected = function( + isConnected: {}, + + isConnectionMetered: {}, +}; + +if (Platform.OS === 'ios') { + var _isConnected = function( reachability: ReachabilityStateIOS ): bool { return reachability !== 'none' && reachability !== 'unknown'; }; +} else if (Platform.OS === 'android') { + var _isConnected = function( + connectionType: ConnectivityStateAndroid + ): bool { + return connectionType !== 'NONE' && connectionType !== 'UNKNOWN'; + }; +} - NetInfo.isConnected = { - addEventListener: function ( - eventName: ChangeEventName, - handler: Function - ): void { - _isConnectedSubscriptions[handler] = (reachability) => { - handler(_iosReachabilityIsConnected(reachability)); - }; - NetInfo.reachabilityIOS.addEventListener( - eventName, - _isConnectedSubscriptions[handler] - ); - }, +var _isConnectedSubscriptions = {}; - removeEventListener: function( - eventName: ChangeEventName, - handler: Function - ): void { - NetInfo.reachabilityIOS.removeEventListener( - eventName, - _isConnectedSubscriptions[handler] - ); - }, +NetInfo.isConnected = { + addEventListener: function ( + eventName: ChangeEventName, + handler: Function + ): void { + _isConnectedSubscriptions[handler] = (connection) => { + handler(_isConnected(connection)); + }; + NetInfo.addEventListener( + eventName, + _isConnectedSubscriptions[handler] + ); + }, - fetch: function(): Promise { - return NetInfo.reachabilityIOS.fetch().then( - (reachability) => _iosReachabilityIsConnected(reachability) - ); - }, + removeEventListener: function( + eventName: ChangeEventName, + handler: Function + ): void { + NetInfo.removeEventListener( + eventName, + _isConnectedSubscriptions[handler] + ); + }, + + fetch: function(): Promise { + return NetInfo.fetch().then( + (connection) => _isConnected(connection) + ); + }, +}; + +if (Platform.OS === 'android') { + NetInfo.isConnectionMetered = function(callback): void { + RCTNetInfo.isConnectionMetered((_isMetered) => { + callback(_isMetered); + }); }; } From 2dfa3b34a15039ded46d8e67776228c8c87670f6 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 2 Jun 2015 06:13:41 -0700 Subject: [PATCH 18/30] [ReactNative] Track bridge events' flow Summary: @public Use trace-viewer's flow events to link the bridge calls Test Plan: {F22498582} --- React/Base/RCTBridge.m | 60 +++++++++++++++++++++++++++-------------- React/Base/RCTDefines.h | 6 +++++ React/Base/RCTProfile.h | 18 +++++++++++++ React/Base/RCTProfile.m | 28 +++++++++++++++++++ 4 files changed, 92 insertions(+), 20 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index f122611cd..5047c0cd5 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1339,8 +1339,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin * AnyThread */ + RCTProfileBeginFlowEvent(); + __weak RCTBatchedBridge *weakSelf = self; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); RCTBatchedBridge *strongSelf = weakSelf; @@ -1348,13 +1351,17 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return; } - id call = @{ - @"module": module, - @"method": method, - @"args": args, - @"context": context ?: @0, - }; + RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();) + id call = @{ + @"js_args": @{ + @"module": module, + @"method": method, + @"args": args, + }, + @"context": context ?: @0, + RCT_IF_DEV(@"call_id": callID,) + }; if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) { strongSelf->_scheduledCallbacks[args[0]] = call; } else { @@ -1490,8 +1497,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return NO; } + RCTProfileBeginFlowEvent(); __weak RCTBatchedBridge *weakSelf = self; [self dispatchBlock:^{ + RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); RCTBatchedBridge *strongSelf = weakSelf; @@ -1526,30 +1535,41 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin { RCTAssertJSThread(); - RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); - - RCTProfileBeginEvent(); - - RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; - for (id observer in _frameUpdateObservers) { - if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) { - [self dispatchBlock:^{ - [observer didUpdateFrame:frameUpdate]; - } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; - } - } - NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor); calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) { return [call[@"context"] isEqualToNumber:currentExecutorID]; }]]; + + RCT_IF_DEV( + RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); + + for (NSDictionary *call in calls) { + _RCTProfileEndFlowEvent(call[@"call_id"]); + } + ) + RCTProfileBeginEvent(); + + RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; + for (id observer in _frameUpdateObservers) { + if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) { + RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) + RCTProfileBeginFlowEvent(); + [self dispatchBlock:^{ + RCTProfileEndFlowEvent(); + RCTProfileBeginEvent(); + [observer didUpdateFrame:frameUpdate]; + RCTProfileEndEvent(name, @"objc_call,fps", nil); + } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; + } + } + if (calls.count > 0) { _scheduledCalls = [[NSMutableArray alloc] init]; _scheduledCallbacks = [[RCTSparseArray alloc] init]; [self _actuallyInvokeAndProcessModule:@"BatchedBridge" method:@"processBatch" - arguments:@[calls] + arguments:@[[calls valueForKey:@"js_args"]] context:RCTGetExecutorID(_javaScriptExecutor)]; } diff --git a/React/Base/RCTDefines.h b/React/Base/RCTDefines.h index 71550a30d..0d985db8a 100644 --- a/React/Base/RCTDefines.h +++ b/React/Base/RCTDefines.h @@ -42,6 +42,12 @@ #endif #endif +#if RCT_DEV +#define RCT_IF_DEV(...) __VA_ARGS__ +#else +#define RCT_IF_DEV(...) +#endif + /** * By default, only raise an NSAssertion in debug mode * (custom assert functions will still be called). diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 0c254c80a..e70217f20 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -22,6 +22,18 @@ #if RCT_DEV +#define RCTProfileBeginFlowEvent() \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wshadow\"") \ +NSNumber *__rct_profile_flow_id = _RCTProfileBeginFlowEvent(); \ +_Pragma("clang diagnostic pop") + +#define RCTProfileEndFlowEvent() \ +_RCTProfileEndFlowEvent(__rct_profile_flow_id) + +RCT_EXTERN NSNumber *_RCTProfileBeginFlowEvent(void); +RCT_EXTERN void _RCTProfileEndFlowEvent(NSNumber *); + /** * Returns YES if the profiling information is currently being collected */ @@ -88,6 +100,12 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString * #else +#define RCTProfileBeginFlowEvent() +#define _RCTProfileBeginFlowEvent() @0 + +#define RCTProfileEndFlowEvent() +#define _RCTProfileEndFlowEvent() + #define RCTProfileIsProfiling(...) NO #define RCTProfileInit(...) #define RCTProfileEnd(...) @"" diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 29e606e86..5be545995 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -171,4 +171,32 @@ void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString ); } +NSNumber *_RCTProfileBeginFlowEvent(void) +{ + static NSUInteger flowID = 0; + + CHECK(@0); + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": @"flow", + @"id": @(++flowID), + @"cat": @"flow", + @"ph": @"s", + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + ); + + return @(flowID); +} + +void _RCTProfileEndFlowEvent(NSNumber *flowID) +{ + CHECK(); + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": @"flow", + @"id": flowID, + @"cat": @"flow", + @"ph": @"f", + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + ); +} + #endif From 0f16d15d6475e52c76e8820beeb5f6811db045a1 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 2 Jun 2015 06:15:53 -0700 Subject: [PATCH 19/30] [ReactNative] Optimize console.profile and add markers on JS entry points Summary: @public Right now the profiler shows how long the executor took on JS but doesn't show how long each of the batched calls took, this adds a *very* high level view of JS execution (still doesn't show properly calls dispatched with setImmediate) Also added a global property on JS to avoid trips to Native when profiling is disabled. Test Plan: Run the Profiler on any app {F22491690} --- Libraries/Utilities/BridgeProfiling.js | 37 ++++++++++++++++++++++++ Libraries/Utilities/MessageQueue.js | 14 ++++++++- React/Base/RCTProfile.h | 3 ++ React/Base/RCTProfile.m | 9 ++++++ React/Executors/RCTContextExecutor.m | 39 +++++++++++++++++++++++--- React/Modules/RCTUIManager.m | 4 +++ 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 Libraries/Utilities/BridgeProfiling.js diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js new file mode 100644 index 000000000..e3c47907b --- /dev/null +++ b/Libraries/Utilities/BridgeProfiling.js @@ -0,0 +1,37 @@ +/** + * 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. + * + * @providesModule BridgeProfiling + * @flow + */ +'use strict'; + +var GLOBAL = GLOBAL || this; + +var BridgeProfiling = { + profile(profileName: String, args?: any) { + if (GLOBAL.__BridgeProfilingIsProfiling) { + if (args) { + try { + args = JSON.stringify(args); + } catch(err) { + args = err.message; + } + } + console.profile(profileName, args); + } + }, + + profileEnd() { + if (GLOBAL.__BridgeProfilingIsProfiling) { + console.profileEnd(); + } + }, +}; + +module.exports = BridgeProfiling; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 9f07cf4c0..1972a0da0 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -17,6 +17,7 @@ var ReactUpdates = require('ReactUpdates'); var invariant = require('invariant'); var warning = require('warning'); +var BridgeProfiling = require('BridgeProfiling'); var JSTimersExecution = require('JSTimersExecution'); var INTERNAL_ERROR = 'Error in MessageQueue implementation'; @@ -277,7 +278,9 @@ var MessageQueueMixin = { if (DEBUG_SPY_MODE) { console.log('N->JS: Callback#' + cbID + '(' + JSON.stringify(args) + ')'); } + BridgeProfiling.profile('Callback#' + cbID + '(' + JSON.stringify(args) + ')'); cb.apply(scope, args); + BridgeProfiling.profileEnd(); } catch(ie_requires_catch) { throw ie_requires_catch; } finally { @@ -311,7 +314,9 @@ var MessageQueueMixin = { 'N->JS: ' + moduleName + '.' + methodName + '(' + JSON.stringify(params) + ')'); } + BridgeProfiling.profile(moduleName + '.' + methodName + '(' + JSON.stringify(params) + ')'); var ret = jsCall(this._requireFunc(moduleName), methodName, params); + BridgeProfiling.profileEnd(); return ret; }, @@ -330,7 +335,8 @@ var MessageQueueMixin = { processBatch: function(batch) { var self = this; - return guardReturn(function () { + BridgeProfiling.profile('MessageQueue.processBatch()'); + var flushedQueue = guardReturn(function () { ReactUpdates.batchedUpdates(function() { batch.forEach(function(call) { invariant( @@ -346,8 +352,12 @@ var MessageQueueMixin = { 'Unrecognized method called on BatchedBridge: ' + call.method); } }); + BridgeProfiling.profile('React.batchedUpdates()'); }); + BridgeProfiling.profileEnd(); }, null, this._flushedQueueUnguarded, this); + BridgeProfiling.profileEnd(); + return flushedQueue; }, setLoggingEnabled: function(enabled) { @@ -472,10 +482,12 @@ var MessageQueueMixin = { }, _flushedQueueUnguarded: function() { + BridgeProfiling.profile('JSTimersExecution.callImmediates()'); ReactUpdates.batchedUpdates(() => { // Call the functions registered via setImmediate JSTimersExecution.callImmediates(); }); + BridgeProfiling.profileEnd(); var currentOutgoingItems = this._outgoingItems; this._swapAndReinitializeBuffer(); diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index e70217f20..f722b0a02 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -20,6 +20,9 @@ * before before using it. */ +NSString *const RCTProfileDidStartProfiling; +NSString *const RCTProfileDidEndProfiling; + #if RCT_DEV #define RCTProfileBeginFlowEvent() \ diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 5be545995..09989d1cb 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -17,6 +17,9 @@ #import "RCTDefines.h" #import "RCTUtils.h" +NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling"; +NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; + #if RCT_DEV #pragma mark - Prototypes @@ -113,10 +116,16 @@ void RCTProfileInit(void) RCTProfileSamples: [[NSMutableArray alloc] init], }; ); + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling + object:nil]; } NSString *RCTProfileEnd(void) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling + object:nil]; + RCTProfileLock( NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); RCTProfileEventID = 0; diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 3a2208739..627255c37 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -133,22 +133,27 @@ static JSValueRef RCTConsoleProfile(JSContextRef context, JSObjectRef object, JS profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++]; } - [profiles addObjectsFromArray:@[profileName, profileID]]; + id profileInfo = [NSNull null]; + if (argumentCount > 1 && !JSValueIsUndefined(context, arguments[1])) { + profileInfo = @[RCTJSValueToNSString(context, arguments[1])]; + } + + [profiles addObjectsFromArray:@[profileName, profileID, profileInfo]]; - RCTLog(@"Profile '%@' finished.", profileName); return JSValueMakeUndefined(context); } static JSValueRef RCTConsoleProfileEnd(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + NSString *profileInfo = [profiles lastObject]; + [profiles removeLastObject]; NSNumber *profileID = [profiles lastObject]; [profiles removeLastObject]; NSString *profileName = [profiles lastObject]; [profiles removeLastObject]; - _RCTProfileEndEvent(profileID, profileName, @"console", nil); + _RCTProfileEndEvent(profileID, profileName, @"console", profileInfo); - RCTLog(@"Profile '%@' started.", profileName); return JSValueMakeUndefined(context); } @@ -244,6 +249,13 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) #if RCT_DEV [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; + + for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(toggleProfilingFlag:) + name:event + object:nil]; + } #endif }]; @@ -252,6 +264,21 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) return self; } +- (void)toggleProfilingFlag:(NSNotification *)notification +{ + JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); + + bool enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling]; + JSStringRef JSName = JSStringCreateWithUTF8CString("__BridgeProfilingIsProfiling"); + JSObjectSetProperty(_context.ctx, + globalObject, + JSName, + JSValueMakeBoolean(_context.ctx, enabled), + kJSPropertyAttributeNone, + NULL); + JSStringRelease(JSName); +} + - (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name { JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); @@ -269,6 +296,10 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) - (void)invalidate { +#if RCT_DEV + [[NSNotificationCenter defaultCenter] removeObserver:self]; +#endif + [_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO]; } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 76e253a76..cc580e903 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -921,6 +921,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call - (void)batchDidComplete { + RCTProfileBeginEvent(); // Gather blocks to be executed now that all view hierarchy manipulations have // been completed (note that these may still take place before layout has finished) for (RCTViewManager *manager in _viewManagers.allValues) { @@ -951,6 +952,9 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call _nextLayoutAnimation = nil; } + RCTProfileEndEvent(@"[RCTUIManager batchDidComplete]", @"uimanager", @{ + @"view_count": @([_viewRegistry count]), + }); [self flushUIBlocks]; } From c094a156d6d61c07eb4fed503311d8080e46a996 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Tue, 2 Jun 2015 07:22:19 -0700 Subject: [PATCH 20/30] [react_native] JS files from D2036695: [ReactNative] Implement new transform API on Android --- Examples/UIExplorer/TransformExample.js | 159 ++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Examples/UIExplorer/TransformExample.js diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/TransformExample.js new file mode 100644 index 000000000..a59a019b3 --- /dev/null +++ b/Examples/UIExplorer/TransformExample.js @@ -0,0 +1,159 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule TransformExample + */ +'use strict'; + +var React = require('React'); + +var StyleSheet = require('StyleSheet'); +var TimerMixin = require('react-timer-mixin'); +var UIExplorerBlock = require('UIExplorerBlock'); +var UIExplorerPage = require('UIExplorerPage'); +var View = require('View'); + +var TransformExample = React.createClass({ + + mixins: [TimerMixin], + + getInitialState() { + return { + interval: this.setInterval(this._update, 800), + pulse: false, + }; + }, + + render() { + return ( + + + + + + + + + + + + + + ); + }, + + _update() { + this.setState({ + pulse: !this.state.pulse, + }); + }, + +}); + +var styles = StyleSheet.create({ + box1: { + left: 0, + backgroundColor: 'green', + height: 50, + position: 'absolute', + top: 0, + transform: [ + {translateX: 100}, + {translateY: 50}, + {rotate: '30deg'}, + {scaleX: 2}, + {scaleY: 2}, + ], + width: 50, + }, + box2: { + left: 0, + backgroundColor: 'purple', + height: 50, + position: 'absolute', + top: 0, + transform: [ + {scaleX: 2}, + {scaleY: 2}, + {translateX: 100}, + {translateY: 50}, + {rotate: '30deg'}, + ], + width: 50, + }, + box3step1: { + left: 0, + backgroundColor: '#ffb6c1', // lightpink + height: 50, + position: 'absolute', + top: 0, + transform: [ + {rotate: '30deg'}, + ], + width: 50, + }, + box3step2: { + left: 0, + backgroundColor: '#ff69b4', //hotpink + height: 50, + opacity: 0.5, + position: 'absolute', + top: 0, + transform: [ + {rotate: '30deg'}, + {scaleX: 2}, + {scaleY: 2}, + ], + width: 50, + }, + box3step3: { + left: 0, + backgroundColor: '#ff1493', // deeppink + height: 50, + opacity: 0.5, + position: 'absolute', + top: 0, + transform: [ + {rotate: '30deg'}, + {scaleX: 2}, + {scaleY: 2}, + {translateX: 100}, + {translateY: 50}, + ], + width: 50, + }, + box4: { + left: 0, + backgroundColor: '#ff8c00', // darkorange + height: 50, + position: 'absolute', + top: 0, + transform: [ + {translate: [200, 350]}, + {scale: 2.5}, + {rotate: '-0.2rad'}, + ], + width: 100, + }, + box5: { + backgroundColor: '#800000', // maroon + height: 50, + position: 'absolute', + right: 0, + top: 0, + width: 50, + }, + box5Transform: { + transform: [ + {translate: [-50, 35]}, + {rotate: '50deg'}, + {scale: 2}, + ], + }, +}); + + +module.exports = TransformExample; From 9b455c8677151fdd86eda82fde3b5f2b697f1868 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Tue, 2 Jun 2015 07:28:56 -0700 Subject: [PATCH 21/30] [react_native] JS files from D2115306: implement ExceptionsManager.report(Fatal|Soft)Exception --- .../Initialization/ExceptionsManager.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index 224ce0966..ff2383a4c 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -30,17 +30,10 @@ function reportException(e: Exception, isFatal: bool, stack?: any) { if (!stack) { stack = parseErrorStack(e); } - if (!RCTExceptionsManager.reportFatalException || - !RCTExceptionsManager.reportSoftException) { - // Backwards compatibility - no differentiation - // TODO(#7049989): deprecate reportUnhandledException on Android - RCTExceptionsManager.reportUnhandledException(e.message, stack); + if (isFatal) { + RCTExceptionsManager.reportFatalException(e.message, stack); } else { - if (isFatal) { - RCTExceptionsManager.reportFatalException(e.message, stack); - } else { - RCTExceptionsManager.reportSoftException(e.message, stack); - } + RCTExceptionsManager.reportSoftException(e.message, stack); } if (__DEV__) { (sourceMapPromise = sourceMapPromise || loadSourceMap()) From 4d4f2dfe09ed2c63b7d92c287634be55dba5f453 Mon Sep 17 00:00:00 2001 From: Chace Liang Date: Tue, 2 Jun 2015 10:30:14 -0700 Subject: [PATCH 22/30] Revert "[ReactNative] Track bridge events' flow" --- React/Base/RCTBridge.m | 42 +++++++++++------------------------------ React/Base/RCTDefines.h | 6 ------ React/Base/RCTProfile.h | 18 ------------------ React/Base/RCTProfile.m | 28 --------------------------- 4 files changed, 11 insertions(+), 83 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 5047c0cd5..f122611cd 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1339,11 +1339,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin * AnyThread */ - RCTProfileBeginFlowEvent(); - __weak RCTBatchedBridge *weakSelf = self; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ - RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); RCTBatchedBridge *strongSelf = weakSelf; @@ -1351,17 +1348,13 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return; } - - RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();) id call = @{ - @"js_args": @{ - @"module": module, - @"method": method, - @"args": args, - }, + @"module": module, + @"method": method, + @"args": args, @"context": context ?: @0, - RCT_IF_DEV(@"call_id": callID,) }; + if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) { strongSelf->_scheduledCallbacks[args[0]] = call; } else { @@ -1497,10 +1490,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return NO; } - RCTProfileBeginFlowEvent(); __weak RCTBatchedBridge *weakSelf = self; [self dispatchBlock:^{ - RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); RCTBatchedBridge *strongSelf = weakSelf; @@ -1535,41 +1526,30 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin { RCTAssertJSThread(); - NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; - NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor); - calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) { - return [call[@"context"] isEqualToNumber:currentExecutorID]; - }]]; + RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); - RCT_IF_DEV( - RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); - - for (NSDictionary *call in calls) { - _RCTProfileEndFlowEvent(call[@"call_id"]); - } - ) RCTProfileBeginEvent(); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; for (id observer in _frameUpdateObservers) { if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) { - RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) - RCTProfileBeginFlowEvent(); [self dispatchBlock:^{ - RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); [observer didUpdateFrame:frameUpdate]; - RCTProfileEndEvent(name, @"objc_call,fps", nil); } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; } } + NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; + NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor); + calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) { + return [call[@"context"] isEqualToNumber:currentExecutorID]; + }]]; if (calls.count > 0) { _scheduledCalls = [[NSMutableArray alloc] init]; _scheduledCallbacks = [[RCTSparseArray alloc] init]; [self _actuallyInvokeAndProcessModule:@"BatchedBridge" method:@"processBatch" - arguments:@[[calls valueForKey:@"js_args"]] + arguments:@[calls] context:RCTGetExecutorID(_javaScriptExecutor)]; } diff --git a/React/Base/RCTDefines.h b/React/Base/RCTDefines.h index 0d985db8a..71550a30d 100644 --- a/React/Base/RCTDefines.h +++ b/React/Base/RCTDefines.h @@ -42,12 +42,6 @@ #endif #endif -#if RCT_DEV -#define RCT_IF_DEV(...) __VA_ARGS__ -#else -#define RCT_IF_DEV(...) -#endif - /** * By default, only raise an NSAssertion in debug mode * (custom assert functions will still be called). diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index f722b0a02..6ac9d1c0f 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -25,18 +25,6 @@ NSString *const RCTProfileDidEndProfiling; #if RCT_DEV -#define RCTProfileBeginFlowEvent() \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSNumber *__rct_profile_flow_id = _RCTProfileBeginFlowEvent(); \ -_Pragma("clang diagnostic pop") - -#define RCTProfileEndFlowEvent() \ -_RCTProfileEndFlowEvent(__rct_profile_flow_id) - -RCT_EXTERN NSNumber *_RCTProfileBeginFlowEvent(void); -RCT_EXTERN void _RCTProfileEndFlowEvent(NSNumber *); - /** * Returns YES if the profiling information is currently being collected */ @@ -103,12 +91,6 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString * #else -#define RCTProfileBeginFlowEvent() -#define _RCTProfileBeginFlowEvent() @0 - -#define RCTProfileEndFlowEvent() -#define _RCTProfileEndFlowEvent() - #define RCTProfileIsProfiling(...) NO #define RCTProfileInit(...) #define RCTProfileEnd(...) @"" diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 09989d1cb..9b7743494 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -180,32 +180,4 @@ void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString ); } -NSNumber *_RCTProfileBeginFlowEvent(void) -{ - static NSUInteger flowID = 0; - - CHECK(@0); - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": @"flow", - @"id": @(++flowID), - @"cat": @"flow", - @"ph": @"s", - @"ts": RCTProfileTimestamp(CACurrentMediaTime()), - ); - - return @(flowID); -} - -void _RCTProfileEndFlowEvent(NSNumber *flowID) -{ - CHECK(); - RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": @"flow", - @"id": flowID, - @"cat": @"flow", - @"ph": @"f", - @"ts": RCTProfileTimestamp(CACurrentMediaTime()), - ); -} - #endif From 78f83acdc272cd70ec1af1b37f6d48b28045cc8f Mon Sep 17 00:00:00 2001 From: rickyc Date: Tue, 2 Jun 2015 15:14:25 -0700 Subject: [PATCH 23/30] Debugger won't start due to spaces in directory path Summary: Similar issue to #214. When I attempt to do command + D in the simulator, I get the following issue. ``` Launching Dev Tools... Failed to run launchChromeDevTools.applescript { [Error: Command failed: /bin/sh -c /Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packager/launchChromeDevTools.applescript http://localhost:8081/debugger-ui /bin/sh: -c: line 0: syntax error near unexpected token `Personal' /bin/sh: -c: line 0: `/Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packager/launchChromeDevTools.applescript http://localhost:8081/debugger-ui' ] killed: false, code: 2, signal: null, cmd: '/bin/sh -c /Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packager/launchChromeDevTools.applescript http://localhost:8081/debugger-ui' } /bin/sh: -c: line 0: syntax error near unexpected token `Personal' /bin/sh: -c: line 0: `/Users/ricky/Dropbox (Personal)/Sites/AwesomeProject/node_modules/react-native/packa Closes https://github.com/facebook/react-native/pull/348 Github Author: rickyc Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/packager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/packager.js b/packager/packager.js index d630d06e3..7c4be0a77 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -10,7 +10,7 @@ var fs = require('fs'); var path = require('path'); -var exec = require('child_process').exec; +var execFile = require('child_process').execFile; var http = require('http'); var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); @@ -172,7 +172,7 @@ function getDevToolsLauncher(options) { var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui'; var script = 'launchChromeDevTools.applescript'; console.log('Launching Dev Tools...'); - exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) { + execFile(path.join(__dirname, script), [debuggerURL], function(err, stdout, stderr) { if (err) { console.log('Failed to run ' + script, err); } From d90999a989772d149a164b6c5ac010aa84467714 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Tue, 2 Jun 2015 17:51:03 -0700 Subject: [PATCH 24/30] [ReactNative] Disable UIExplorer render test --- Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index 45df4e417..1e2c7311f 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -62,6 +62,9 @@ // Make sure this test runs first because the other tests will tear out the rootView - (void)testAAA_RootViewLoadsAndRenders { + // TODO (t7296305) Fix and Re-Enable this UIExplorer Test + return; + UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first."); NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; From d796584b6c8122ecf6fc6f0de7f82131b2ed51d1 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 2 Jun 2015 17:51:36 -0700 Subject: [PATCH 25/30] [ReactNative] ErrorUtils.reportError shouldn't redbox Summary: @public ErrorUtils.reportError is intended for reporting handled errors to the server, like timeouts, which means that we shouldn't shove them in the developer's face. Test Plan: add `require('ErrorUtils').reportError(new Error('error'))` and see a useful error message with stack but no redbox. Debugger confirms `reportSoftException` is called but it doesn't do anything yet. `reportFatalError` and throwing exceptions still show redboxes. --- .../Initialization/InitializeJavaScriptAppEngine.js | 4 ++-- React/Modules/RCTExceptionsManager.m | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 29d64152e..81978ee0c 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -33,7 +33,7 @@ if (typeof window === 'undefined') { window = GLOBAL; } -function handleErrorWithRedBox(e, isFatal) { +function handleError(e, isFatal) { try { require('ExceptionsManager').handleException(e, isFatal); } catch(ee) { @@ -43,7 +43,7 @@ function handleErrorWithRedBox(e, isFatal) { function setUpRedBoxErrorHandler() { var ErrorUtils = require('ErrorUtils'); - ErrorUtils.setGlobalHandler(handleErrorWithRedBox); + ErrorUtils.setGlobalHandler(handleError); } function setUpRedBoxConsoleErrorHandler() { diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 7512c540d..64a5c85f0 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -44,8 +44,7 @@ RCT_EXPORT_METHOD(reportSoftException:(NSString *)message [_delegate handleSoftJSExceptionWithMessage:message stack:stack]; return; } - - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + // JS already logs the error via console. } RCT_EXPORT_METHOD(reportFatalException:(NSString *)message From 1c82de4db2366ddc6c0665e7ae4e05686fcdf35a Mon Sep 17 00:00:00 2001 From: Caleb Brown Date: Tue, 2 Jun 2015 20:25:39 -0700 Subject: [PATCH 26/30] [SampleApp + UIExplorer] Typos in the tests Summary: Closes https://github.com/facebook/react-native/pull/1465 Github Author: Caleb Brown Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/SampleApp/SampleAppTests/SampleAppTests.m | 2 +- Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/SampleApp/SampleAppTests/SampleAppTests.m b/Examples/SampleApp/SampleAppTests/SampleAppTests.m index 0fa28c29d..f06ffd148 100644 --- a/Examples/SampleApp/SampleAppTests/SampleAppTests.m +++ b/Examples/SampleApp/SampleAppTests/SampleAppTests.m @@ -58,7 +58,7 @@ } XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Cound't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); + XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); } diff --git a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m index 1e2c7311f..d48b25ddf 100644 --- a/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m +++ b/Examples/UIExplorer/UIExplorerTests/UIExplorerTests.m @@ -85,7 +85,7 @@ } XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Cound't find element with '' text in %d seconds", TIMEOUT_SECONDS); + XCTAssertTrue(foundElement, @"Couldn't find element with '' text in %d seconds", TIMEOUT_SECONDS); } #define RCT_SNAPSHOT_TEST(name, reRecord) \ From b2432364048ea59ad0c99c303b463b918aea7f8d Mon Sep 17 00:00:00 2001 From: Klein Lieu Date: Tue, 2 Jun 2015 20:25:51 -0700 Subject: [PATCH 27/30] [Cosmetic] Fixing comment typo Summary: Closes https://github.com/facebook/react-native/pull/780 Github Author: Klein Lieu Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Utilities/ErrorUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Utilities/ErrorUtils.js b/Libraries/Utilities/ErrorUtils.js index b66b08546..3f15ce2ce 100644 --- a/Libraries/Utilities/ErrorUtils.js +++ b/Libraries/Utilities/ErrorUtils.js @@ -22,6 +22,6 @@ var GLOBAL = this; * * However, we still want to treat ErrorUtils as a module so that other modules * that use it aren't just using a global variable, so simply export the global - * variable here. ErrorUtils is original defined in a file named error-guard.js. + * variable here. ErrorUtils is originally defined in a file named error-guard.js. */ module.exports = GLOBAL.ErrorUtils; From 95d28433622898022123dee1039dccafb0907934 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 3 Jun 2015 10:04:25 -0100 Subject: [PATCH 28/30] Revert "[Timers] Batch setImmediate handlers" --- Libraries/Utilities/MessageQueue.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 1972a0da0..a09bd4f4a 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -483,10 +483,8 @@ var MessageQueueMixin = { _flushedQueueUnguarded: function() { BridgeProfiling.profile('JSTimersExecution.callImmediates()'); - ReactUpdates.batchedUpdates(() => { - // Call the functions registered via setImmediate - JSTimersExecution.callImmediates(); - }); + // Call the functions registered via setImmediate + JSTimersExecution.callImmediates(); BridgeProfiling.profileEnd(); var currentOutgoingItems = this._outgoingItems; From 10b13512b9a1c5e30ec1592ad5d32ba8e124e116 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 3 Jun 2015 05:38:24 -0700 Subject: [PATCH 29/30] Simplify RKDataManager --- Libraries/Network/RCTDataManager.m | 1 - Libraries/Network/XMLHttpRequest.ios.js | 4 ---- 2 files changed, 5 deletions(-) diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTDataManager.m index 6a2e39b2d..f4497a187 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTDataManager.m @@ -24,7 +24,6 @@ RCT_EXPORT_MODULE() */ RCT_EXPORT_METHOD(queryData:(NSString *)queryType withQuery:(NSDictionary *)query - queryHash:(__unused NSString *)queryHash responseSender:(RCTResponseSenderBlock)responseSender) { if ([queryType isEqualToString:@"http"]) { diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 64b5ea526..9249047da 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -13,8 +13,6 @@ var RCTDataManager = require('NativeModules').DataManager; -var crc32 = require('crc32'); - var XMLHttpRequestBase = require('XMLHttpRequestBase'); class XMLHttpRequest extends XMLHttpRequestBase { @@ -28,8 +26,6 @@ class XMLHttpRequest extends XMLHttpRequestBase { data: data, headers: headers, }, - // TODO: Do we need this? is it used anywhere? - 'h' + crc32(method + '|' + url + '|' + data), this.callback.bind(this) ); } From 158d8b64ffbfea6c49810af4bcc97c114d990f99 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 3 Jun 2015 05:38:21 -0700 Subject: [PATCH 30/30] [ReactNative] Track bridge events' flow (timers fixed) Summary: @public Use trace-viewer's flow events to link the bridge calls Test Plan: {F22498582} --- React/Base/RCTBridge.m | 40 ++++++++++++++++++++++++++++++---------- React/Base/RCTDefines.h | 6 ++++++ React/Base/RCTProfile.h | 18 ++++++++++++++++++ React/Base/RCTProfile.m | 28 ++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index f122611cd..70868f721 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1339,8 +1339,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin * AnyThread */ + RCTProfileBeginFlowEvent(); + __weak RCTBatchedBridge *weakSelf = self; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); RCTBatchedBridge *strongSelf = weakSelf; @@ -1348,13 +1351,17 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return; } - id call = @{ - @"module": module, - @"method": method, - @"args": args, - @"context": context ?: @0, - }; + RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();) + id call = @{ + @"js_args": @{ + @"module": module, + @"method": method, + @"args": args, + }, + @"context": context ?: @0, + RCT_IF_DEV(@"call_id": callID,) + }; if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) { strongSelf->_scheduledCallbacks[args[0]] = call; } else { @@ -1490,8 +1497,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin return NO; } + RCTProfileBeginFlowEvent(); __weak RCTBatchedBridge *weakSelf = self; [self dispatchBlock:^{ + RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); RCTBatchedBridge *strongSelf = weakSelf; @@ -1525,16 +1534,18 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { RCTAssertJSThread(); - - RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); - RCTProfileBeginEvent(); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; for (id observer in _frameUpdateObservers) { if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) { + RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) + RCTProfileBeginFlowEvent(); [self dispatchBlock:^{ + RCTProfileEndFlowEvent(); + RCTProfileBeginEvent(); [observer didUpdateFrame:frameUpdate]; + RCTProfileEndEvent(name, @"objc_call,fps", nil); } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; } } @@ -1544,12 +1555,21 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) { return [call[@"context"] isEqualToNumber:currentExecutorID]; }]]; + + RCT_IF_DEV( + RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); + + for (NSDictionary *call in calls) { + _RCTProfileEndFlowEvent(call[@"call_id"]); + } + ) + if (calls.count > 0) { _scheduledCalls = [[NSMutableArray alloc] init]; _scheduledCallbacks = [[RCTSparseArray alloc] init]; [self _actuallyInvokeAndProcessModule:@"BatchedBridge" method:@"processBatch" - arguments:@[calls] + arguments:@[[calls valueForKey:@"js_args"]] context:RCTGetExecutorID(_javaScriptExecutor)]; } diff --git a/React/Base/RCTDefines.h b/React/Base/RCTDefines.h index 71550a30d..0d985db8a 100644 --- a/React/Base/RCTDefines.h +++ b/React/Base/RCTDefines.h @@ -42,6 +42,12 @@ #endif #endif +#if RCT_DEV +#define RCT_IF_DEV(...) __VA_ARGS__ +#else +#define RCT_IF_DEV(...) +#endif + /** * By default, only raise an NSAssertion in debug mode * (custom assert functions will still be called). diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 6ac9d1c0f..f722b0a02 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -25,6 +25,18 @@ NSString *const RCTProfileDidEndProfiling; #if RCT_DEV +#define RCTProfileBeginFlowEvent() \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wshadow\"") \ +NSNumber *__rct_profile_flow_id = _RCTProfileBeginFlowEvent(); \ +_Pragma("clang diagnostic pop") + +#define RCTProfileEndFlowEvent() \ +_RCTProfileEndFlowEvent(__rct_profile_flow_id) + +RCT_EXTERN NSNumber *_RCTProfileBeginFlowEvent(void); +RCT_EXTERN void _RCTProfileEndFlowEvent(NSNumber *); + /** * Returns YES if the profiling information is currently being collected */ @@ -91,6 +103,12 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString * #else +#define RCTProfileBeginFlowEvent() +#define _RCTProfileBeginFlowEvent() @0 + +#define RCTProfileEndFlowEvent() +#define _RCTProfileEndFlowEvent() + #define RCTProfileIsProfiling(...) NO #define RCTProfileInit(...) #define RCTProfileEnd(...) @"" diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 9b7743494..09989d1cb 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -180,4 +180,32 @@ void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString ); } +NSNumber *_RCTProfileBeginFlowEvent(void) +{ + static NSUInteger flowID = 0; + + CHECK(@0); + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": @"flow", + @"id": @(++flowID), + @"cat": @"flow", + @"ph": @"s", + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + ); + + return @(flowID); +} + +void _RCTProfileEndFlowEvent(NSNumber *flowID) +{ + CHECK(); + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": @"flow", + @"id": flowID, + @"cat": @"flow", + @"ph": @"f", + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + ); +} + #endif