From 2cbcfcea8801e4e806084c4041802ffd60713560 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Fri, 10 Jul 2015 12:21:19 -0700 Subject: [PATCH 01/12] [Android][JS]: supports prop "html" in WebView --- Libraries/Components/WebView/WebView.android.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index b525c44f3..274d76728 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -36,7 +36,8 @@ var WebView = React.createClass({ propTypes: { renderError: PropTypes.func, // view to show if there's an error renderLoading: PropTypes.func, // loading indicator to show - url: PropTypes.string.isRequired, + url: PropTypes.string, + html: PropTypes.string, automaticallyAdjustContentInsets: PropTypes.bool, contentInset: EdgeInsetsPropType, onNavigationStateChange: PropTypes.func, @@ -102,6 +103,7 @@ var WebView = React.createClass({ key="webViewKey" style={webViewStyles} url={this.props.url} + html={this.props.html} injectedJavaScript={this.props.injectedJavaScript} userAgent={this.props.userAgent} javaScriptEnabledAndroid={this.props.javaScriptEnabledAndroid} @@ -183,6 +185,7 @@ var WebView = React.createClass({ var RCTWebView = createReactNativeComponentClass({ validAttributes: merge(ReactNativeViewAttributes.UIView, { + html: true, injectedJavaScript: true, javaScriptEnabledAndroid: true, url: true, From 9c4b9973f954fa989aa90597685d5a06434407fe Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 13 Jul 2015 07:38:44 -0700 Subject: [PATCH 02/12] [ReactNative] add _flowconfig and _gitignore to SampleApp --- Examples/SampleApp/_flowconfig | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Examples/SampleApp/_flowconfig b/Examples/SampleApp/_flowconfig index 9d4fb3614..b2a76e029 100644 --- a/Examples/SampleApp/_flowconfig +++ b/Examples/SampleApp/_flowconfig @@ -16,7 +16,7 @@ .*/node_modules/react-tools/src/event/EventPropagators.js # Ignore commoner tests -.*/node_modules/react-tools/node_modules/commoner/test/.* +.*/node_modules/commoner/test/.* # See https://github.com/facebook/flow/issues/442 .*/react-tools/node_modules/commoner/lib/reader.js @@ -31,3 +31,14 @@ node_modules/react-native/Libraries/react-native/react-native-interface.js [options] module.system=haste + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-3]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-3]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +[version] +0.13.1 From 4beef5bec35cfcb949c78d3fb24d2d4b17cbbbe5 Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Mon, 13 Jul 2015 08:16:29 -0700 Subject: [PATCH 03/12] [React Native] Re-enable testScenario{1,2} Summary: The tests are passing locally. Not sure why they weren't passing on FB CI. --- .../UIExplorer.xcodeproj/project.pbxproj | 4 + .../RCTUIManagerScenarioTests.m | 437 ++++++++++++++++++ .../UIExplorerUnitTests/RCTUIManagerTests.m | 348 -------------- 3 files changed, 441 insertions(+), 348 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 5b1d0d240..4897cb72e 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; + 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; }; D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; /* End PBXBuildFile section */ @@ -202,6 +203,7 @@ 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; + 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = ""; }; D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -383,6 +385,7 @@ children = ( 141FC1201B222EBB004D5FFB /* IntegrationTests.m */, 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */, + 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */, 143BC5971B21E3E100462512 /* Supporting Files */, ); path = UIExplorerIntegrationTests; @@ -797,6 +800,7 @@ buildActionMask = 2147483647; files = ( 141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */, + 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */, 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m new file mode 100644 index 000000000..60c238b88 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m @@ -0,0 +1,437 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#import +#import + +#import "RCTShadowView.h" +#import "RCTUIManager.h" +#import "RCTRootView.h" +#import "RCTSparseArray.h" +#import "UIView+React.h" + +@interface RCTUIManager (Testing) + +- (void)_manageChildren:(NSNumber *)containerReactTag + addChildReactTags:(NSArray *)addChildReactTags + addAtIndices:(NSArray *)addAtIndices + removeAtIndices:(NSArray *)removeAtIndices + registry:(RCTSparseArray *)registry; + +- (void)modifyManageChildren:(NSNumber *)containerReactTag + addChildReactTags:(NSMutableArray *)mutableAddChildReactTags + addAtIndices:(NSMutableArray *)mutableAddAtIndices + removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices; + +- (void)createView:(NSNumber *)reactTag + viewName:(NSString *)viewName + rootTag:(NSNumber *)rootTag + props:(NSDictionary *)props; + +- (void)updateView:(NSNumber *)reactTag + viewName:(NSString *)viewName + props:(NSDictionary *)props; + +- (void)manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices + addChildReactTags:(NSArray *)addChildReactTags + addAtIndices:(NSArray *)addAtIndices + removeAtIndices:(NSArray *)removeAtIndices; + +- (void)flushUIBlocks; + +@property (nonatomic, readonly) RCTSparseArray *viewRegistry; +@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only + +@end + +@interface RCTUIManagerScenarioTests : XCTestCase + +@property (nonatomic, readwrite, strong) RCTUIManager *uiManager; + +@end + +@implementation RCTUIManagerScenarioTests + +- (void)setUp +{ + [super setUp]; + + _uiManager = [[RCTUIManager alloc] init]; + + // Register 20 views to use in the tests + for (NSInteger i = 1; i <= 20; i++) { + UIView *registeredView = [[UIView alloc] init]; + [registeredView setReactTag:@(i)]; + _uiManager.viewRegistry[i] = registeredView; + + RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init]; + registeredShadowView.viewName = @"RCTView"; + [registeredShadowView setReactTag:@(i)]; + _uiManager.shadowViewRegistry[i] = registeredShadowView; + } +} + +/* +-----------------------------------------------------------+ +----------------------+ + * | Shadow Hierarchy | | Legend | + * +-----------------------------------------------------------+ +----------------------+ + * | | | | + * | +---+ ****** | | ******************** | + * | | 1 | * 11 * | | * Layout-only View * | + * | +---+ ****** | | ******************** | + * | | | | | | + * | +-------+---+---+----------+ +---+---+ | | +----+ | + * | | | | | | | | | |View| Subview | + * | v v v v v v | | +----+ -----------> | + * | ***** +---+ ***** +---+ +----+ +----+ | | | + * | * 2 * | 3 | * 4 * | 5 | | 12 | | 13 | | +----------------------+ + * | ***** +---+ ***** +---+ +----+ +----+ | + * | | | | | + * | +---+--+ | +---+---+ | + * | | | | | | | + * | v v v v v | + * | +---+ +---+ +---+ +---+ ****** | + * | | 6 | | 7 | | 8 | | 9 | * 10 * | + * | +---+ +---+ +---+ +---+ ****** | + * | | + * +-----------------------------------------------------------+ + * + * +-----------------------------------------------------------+ + * | View Hierarchy | + * +-----------------------------------------------------------+ + * | | + * | +---+ ****** | + * | | 1 | * 11 * | + * | +---+ ****** | + * | | | | + * | +------+------+------+------+ +---+---+ | + * | | | | | | | | | + * | v v v v v v v | + * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | + * | | 6 | | 7 | | 3 | | 8 | | 5 | | 12 | | 13 | | + * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | + * | | | + * | v | + * | +---+ | + * | | 9 | | + * | +---+ | + * | | + * +-----------------------------------------------------------+ + */ + +- (void)updateShadowViewWithReactTag:(NSNumber *)reactTag layoutOnly:(BOOL)isLayoutOnly childTags:(NSArray *)childTags +{ + RCTShadowView *shadowView = _uiManager.shadowViewRegistry[reactTag]; + shadowView.allProps = isLayoutOnly ? @{} : @{@"collapsible": @NO}; + [childTags enumerateObjectsUsingBlock:^(NSNumber *childTag, NSUInteger idx, __unused BOOL *stop) { + [shadowView insertReactSubview:_uiManager.shadowViewRegistry[childTag] atIndex:idx]; + }]; +} + +- (void)setUpShadowViewHierarchy +{ + [self updateShadowViewWithReactTag:@1 layoutOnly:NO childTags:@[@2, @3, @4, @5]]; + [self updateShadowViewWithReactTag:@2 layoutOnly:YES childTags:@[@6, @7]]; + [self updateShadowViewWithReactTag:@3 layoutOnly:NO childTags:nil]; + [self updateShadowViewWithReactTag:@4 layoutOnly:YES childTags:@[@8]]; + [self updateShadowViewWithReactTag:@5 layoutOnly:NO childTags:@[@9, @10]]; + [self updateShadowViewWithReactTag:@6 layoutOnly:NO childTags:nil]; + [self updateShadowViewWithReactTag:@7 layoutOnly:NO childTags:nil]; + [self updateShadowViewWithReactTag:@8 layoutOnly:NO childTags:nil]; + [self updateShadowViewWithReactTag:@9 layoutOnly:NO childTags:nil]; + [self updateShadowViewWithReactTag:@10 layoutOnly:YES childTags:nil]; + [self updateShadowViewWithReactTag:@11 layoutOnly:YES childTags:@[@12, @13]]; + [self updateShadowViewWithReactTag:@12 layoutOnly:NO childTags:nil]; + [self updateShadowViewWithReactTag:@13 layoutOnly:NO childTags:nil]; +} + +- (void)testModifyIndices1 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[@2] mutableCopy]; + NSMutableArray *addIndices = [@[@3] mutableCopy]; + NSMutableArray *removeIndices = [@[@0] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[@6, @7])); + XCTAssertEqualObjects(addIndices, (@[@3, @4])); + XCTAssertEqualObjects(removeIndices, (@[@0, @1])); +} + +- (void)testModifyIndices2 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[@11] mutableCopy]; + NSMutableArray *addIndices = [@[@4] mutableCopy]; + NSMutableArray *removeIndices = [@[] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[@12, @13])); + XCTAssertEqualObjects(addIndices, (@[@5, @6])); + XCTAssertEqualObjects(removeIndices, (@[])); +} + +- (void)testModifyIndices3 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[] mutableCopy]; + NSMutableArray *addIndices = [@[] mutableCopy]; + NSMutableArray *removeIndices = [@[@2] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[])); + XCTAssertEqualObjects(addIndices, (@[])); + XCTAssertEqualObjects(removeIndices, (@[@3])); +} + +- (void)testModifyIndices4 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[@11] mutableCopy]; + NSMutableArray *addIndices = [@[@3] mutableCopy]; + NSMutableArray *removeIndices = [@[@2] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[@12, @13])); + XCTAssertEqualObjects(addIndices, (@[@4, @5])); + XCTAssertEqualObjects(removeIndices, (@[@3])); +} + +- (void)testModifyIndices5 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[] mutableCopy]; + NSMutableArray *addIndices = [@[] mutableCopy]; + NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[])); + XCTAssertEqualObjects(addIndices, (@[])); + XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); +} + +- (void)testModifyIndices6 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[@11] mutableCopy]; + NSMutableArray *addIndices = [@[@0] mutableCopy]; + NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[@12, @13])); + XCTAssertEqualObjects(addIndices, (@[@0, @1])); + XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); +} + +- (void)testModifyIndices7 +{ + [self setUpShadowViewHierarchy]; + + NSMutableArray *addTags = [@[@11] mutableCopy]; + NSMutableArray *addIndices = [@[@1] mutableCopy]; + NSMutableArray *removeIndices = [@[@0, @2, @3] mutableCopy]; + [self.uiManager modifyManageChildren:@1 + addChildReactTags:addTags + addAtIndices:addIndices + removeAtIndices:removeIndices]; + XCTAssertEqualObjects(addTags, (@[@12, @13])); + XCTAssertEqualObjects(addIndices, (@[@1, @2])); + XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4])); +} + +- (void)testScenario1 +{ + RCTUIManager *uiManager = [[RCTUIManager alloc] init]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; + + __block BOOL done = NO; + + dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; + dispatch_async(shadowQueue, ^{ + // Make sure root view finishes loading. + dispatch_sync(dispatch_get_main_queue(), ^{}); + + /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; + /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; + /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; + /* V */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"blue",@"height":@50,@"width":@50}]; + /* */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; + /* V */[uiManager createView:@7 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@10,@"margin":@50}]; + /* V */[uiManager createView:@8 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"yellow",@"height":@50}]; + /* V */[uiManager createView:@9 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; + /* */[uiManager createView:@10 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; + /* */[uiManager manageChildren:@9 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@10] addAtIndices:@[@0] removeAtIndices:nil]; + /* V */[uiManager createView:@12 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"green",@"height":@50}]; + /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8,@9,@12] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; + /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; + /* V */[uiManager createView:@13 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"red",@"height":@50,@"width":@50}]; + /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5,@6,@13] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; + /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; + + [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { + XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); + XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); + done = YES; + }]; + + [uiManager flushUIBlocks]; + }); + + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0]; + while ([date timeIntervalSinceNow] > 0 && !done) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + + done = NO; + dispatch_async(shadowQueue, ^{ + [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; + [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { + XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); + XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)7); + done = YES; + }]; + + [uiManager flushUIBlocks]; + }); + + date = [NSDate dateWithTimeIntervalSinceNow:1.0]; + while ([date timeIntervalSinceNow] > 0 && !done) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + + done = NO; + dispatch_async(shadowQueue, ^{ + [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":@10}]; + [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { + XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); + XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); + done = YES; + }]; + + [uiManager flushUIBlocks]; + }); + + date = [NSDate dateWithTimeIntervalSinceNow:1.0]; + while ([date timeIntervalSinceNow] > 0 && !done) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } +} + +- (void)testScenario2 +{ + RCTUIManager *uiManager = [[RCTUIManager alloc] init]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; + + __block BOOL done = NO; + + dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; + dispatch_async(shadowQueue, ^{ + // Make sure root view finishes loading. + dispatch_sync(dispatch_get_main_queue(), ^{}); + + /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; + /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; + /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; + /* */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; + /* V */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@1}]; + /* V */[uiManager createView:@7 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; + /* */[uiManager createView:@8 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; + /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@5 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@6] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; + /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; + + [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { + XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); + XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); + done = YES; + }]; + + [uiManager flushUIBlocks]; + }); + + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0]; + while ([date timeIntervalSinceNow] > 0 && !done) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + + done = NO; + dispatch_async(shadowQueue, ^{ + [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; + [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { + XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); + XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)3); + done = YES; + }]; + + [uiManager flushUIBlocks]; + }); + + date = [NSDate dateWithTimeIntervalSinceNow:1.0]; + while ([date timeIntervalSinceNow] > 0 && !done) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + + done = NO; + dispatch_async(shadowQueue, ^{ + [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":@1}]; + [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { + XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); + XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); + done = YES; + }]; + + [uiManager flushUIBlocks]; + }); + + date = [NSDate dateWithTimeIntervalSinceNow:1.0]; + while ([date timeIntervalSinceNow] > 0 && !done) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index db4118a68..6b788fad1 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -16,29 +16,6 @@ removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry; -- (void)modifyManageChildren:(NSNumber *)containerReactTag - addChildReactTags:(NSMutableArray *)mutableAddChildReactTags - addAtIndices:(NSMutableArray *)mutableAddAtIndices - removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices; - -- (void)createView:(NSNumber *)reactTag - viewName:(NSString *)viewName - rootTag:(NSNumber *)rootTag - props:(NSDictionary *)props; - -- (void)updateView:(NSNumber *)reactTag - viewName:(NSString *)viewName - props:(NSDictionary *)props; - -- (void)manageChildren:(NSNumber *)containerReactTag - moveFromIndices:(NSArray *)moveFromIndices - moveToIndices:(NSArray *)moveToIndices - addChildReactTags:(NSArray *)addChildReactTags - addAtIndices:(NSArray *)addAtIndices - removeAtIndices:(NSArray *)removeAtIndices; - -- (void)flushUIBlocks; - @property (nonatomic, readonly) RCTSparseArray *viewRegistry; @property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only @@ -197,329 +174,4 @@ } } -/* +-----------------------------------------------------------+ +----------------------+ - * | Shadow Hierarchy | | Legend | - * +-----------------------------------------------------------+ +----------------------+ - * | | | | - * | +---+ ****** | | ******************** | - * | | 1 | * 11 * | | * Layout-only View * | - * | +---+ ****** | | ******************** | - * | | | | | | - * | +-------+---+---+----------+ +---+---+ | | +----+ | - * | | | | | | | | | |View| Subview | - * | v v v v v v | | +----+ -----------> | - * | ***** +---+ ***** +---+ +----+ +----+ | | | - * | * 2 * | 3 | * 4 * | 5 | | 12 | | 13 | | +----------------------+ - * | ***** +---+ ***** +---+ +----+ +----+ | - * | | | | | - * | +---+--+ | +---+---+ | - * | | | | | | | - * | v v v v v | - * | +---+ +---+ +---+ +---+ ****** | - * | | 6 | | 7 | | 8 | | 9 | * 10 * | - * | +---+ +---+ +---+ +---+ ****** | - * | | - * +-----------------------------------------------------------+ - * - * +-----------------------------------------------------------+ - * | View Hierarchy | - * +-----------------------------------------------------------+ - * | | - * | +---+ ****** | - * | | 1 | * 11 * | - * | +---+ ****** | - * | | | | - * | +------+------+------+------+ +---+---+ | - * | | | | | | | | | - * | v v v v v v v | - * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | - * | | 6 | | 7 | | 3 | | 8 | | 5 | | 12 | | 13 | | - * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | - * | | | - * | v | - * | +---+ | - * | | 9 | | - * | +---+ | - * | | - * +-----------------------------------------------------------+ - */ - -- (void)updateShadowViewWithReactTag:(NSNumber *)reactTag layoutOnly:(BOOL)isLayoutOnly childTags:(NSArray *)childTags -{ - RCTShadowView *shadowView = _uiManager.shadowViewRegistry[reactTag]; - shadowView.allProps = isLayoutOnly ? @{} : @{@"collapsible": @NO}; - [childTags enumerateObjectsUsingBlock:^(NSNumber *childTag, NSUInteger idx, __unused BOOL *stop) { - [shadowView insertReactSubview:_uiManager.shadowViewRegistry[childTag] atIndex:idx]; - }]; -} - -- (void)setUpShadowViewHierarchy -{ - [self updateShadowViewWithReactTag:@1 layoutOnly:NO childTags:@[@2, @3, @4, @5]]; - [self updateShadowViewWithReactTag:@2 layoutOnly:YES childTags:@[@6, @7]]; - [self updateShadowViewWithReactTag:@3 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@4 layoutOnly:YES childTags:@[@8]]; - [self updateShadowViewWithReactTag:@5 layoutOnly:NO childTags:@[@9, @10]]; - [self updateShadowViewWithReactTag:@6 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@7 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@8 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@9 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@10 layoutOnly:YES childTags:nil]; - [self updateShadowViewWithReactTag:@11 layoutOnly:YES childTags:@[@12, @13]]; - [self updateShadowViewWithReactTag:@12 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@13 layoutOnly:NO childTags:nil]; -} - -- (void)testModifyIndices1 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@2] mutableCopy]; - NSMutableArray *addIndices = [@[@3] mutableCopy]; - NSMutableArray *removeIndices = [@[@0] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@6, @7])); - XCTAssertEqualObjects(addIndices, (@[@3, @4])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1])); -} - -- (void)testModifyIndices2 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@4] mutableCopy]; - NSMutableArray *removeIndices = [@[] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@5, @6])); - XCTAssertEqualObjects(removeIndices, (@[])); -} - -- (void)testModifyIndices3 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[] mutableCopy]; - NSMutableArray *addIndices = [@[] mutableCopy]; - NSMutableArray *removeIndices = [@[@2] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[])); - XCTAssertEqualObjects(addIndices, (@[])); - XCTAssertEqualObjects(removeIndices, (@[@3])); -} - -- (void)testModifyIndices4 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@3] mutableCopy]; - NSMutableArray *removeIndices = [@[@2] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@4, @5])); - XCTAssertEqualObjects(removeIndices, (@[@3])); -} - -- (void)testModifyIndices5 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[] mutableCopy]; - NSMutableArray *addIndices = [@[] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[])); - XCTAssertEqualObjects(addIndices, (@[])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); -} - -- (void)testModifyIndices6 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@0] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@0, @1])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); -} - -- (void)testModifyIndices7 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@1] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@1, @2])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4])); -} - -- (void)DISABLED_testScenario1 // t7660646 -{ - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@""]; - - dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; - dispatch_async(shadowQueue, ^{ - // Make sure root view finishes loading. - dispatch_sync(dispatch_get_main_queue(), ^{}); - - /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; - /* V */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"blue",@"height":@50,@"width":@50}]; - /* */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; - /* V */[uiManager createView:@7 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@10,@"margin":@50}]; - /* V */[uiManager createView:@8 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"yellow",@"height":@50}]; - /* V */[uiManager createView:@9 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; - /* */[uiManager createView:@10 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; - /* */[uiManager manageChildren:@9 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@10] addAtIndices:@[@0] removeAtIndices:nil]; - /* V */[uiManager createView:@12 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"green",@"height":@50}]; - /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8,@9,@12] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; - /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; - /* V */[uiManager createView:@13 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"red",@"height":@50,@"width":@50}]; - /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5,@6,@13] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; - /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; - - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)7); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":@10}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)DISABLED_testScenario2 // t7660646 -{ - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@""]; - - dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; - dispatch_async(shadowQueue, ^{ - // Make sure root view finishes loading. - dispatch_sync(dispatch_get_main_queue(), ^{}); - - /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; - /* */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; - /* V */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@1}]; - /* V */[uiManager createView:@7 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; - /* */[uiManager createView:@8 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; - /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@5 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@6] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; - - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)3); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":@1}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - @end From 01151f8c7a9de00c5922078acf13baa052aa6b21 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 13 Jul 2015 08:42:32 -0700 Subject: [PATCH 04/12] Added LRU cache to fix out of memory issues with color caching --- .../UIExplorer.xcodeproj/project.pbxproj | 8 + .../UIExplorerUnitTests/RCTAllocationTests.m | 14 +- .../UIExplorerUnitTests/RCTBridgeTests.m | 14 +- .../UIExplorerUnitTests/RCTCacheTests.m | 168 +++++++++ .../RCTContextExecutorTests.m | 14 +- .../RCTConvert_NSURLTests.m | 14 +- .../RCTConvert_UIFontTests.m | 16 +- .../RCTEventDispatcherTests.m | 17 +- .../UIExplorerUnitTests/RCTShadowViewTests.m | 14 +- .../UIExplorerUnitTests/RCTSparseArrayTests.m | 14 +- .../UIExplorerUnitTests/RCTUIManagerTests.m | 14 +- Libraries/Image/RCTDownloadTaskWrapper.m | 2 +- Libraries/Image/RCTImageDownloader.m | 17 +- React/Base/RCTCache.h | 51 +++ React/Base/RCTCache.m | 326 ++++++++++++++++++ React/Base/RCTConvert.m | 6 +- React/React.xcodeproj/project.pbxproj | 6 + 17 files changed, 687 insertions(+), 28 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m create mode 100644 React/Base/RCTCache.h create mode 100644 React/Base/RCTCache.m diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 4897cb72e..db89c32b8 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; 134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; }; 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; + 138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; }; + 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; }; 139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; @@ -159,6 +161,8 @@ 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; + 138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = ""; }; + 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = ""; }; 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = ""; }; @@ -349,8 +353,10 @@ 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */, 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */, 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */, + 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */, 1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */, 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, + 138D6A151B53CD440074A87E /* RCTCacheTests.m */, 143BC57E1B21E18100462512 /* Info.plist */, 14D6D7101B220EB3001FB087 /* libOCMock.a */, 14D6D7011B220AE3001FB087 /* OCMock */, @@ -782,7 +788,9 @@ 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */, 1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */, 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */, + 138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */, 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */, + 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index aac517fc9..14cc34484 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index 13bcdd7b4..280aaf3b3 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m new file mode 100644 index 000000000..30313d23b --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m @@ -0,0 +1,168 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import "RCTCache.h" + +// Silence silly sign warnings when using int literals +#pragma clang diagnostic ignored "-Wsign-compare" + +@interface RCTCache (Private) + +- (void)cleanUpAllObjects; +- (void)resequence; +- (NSDictionary *)cache; +- (void)setSequenceNumber:(NSInteger)number; + +@end + +@interface RCTCacheTests : XCTestCase + +@property (nonatomic, strong) RCTCache *cache; + +@end + +@implementation RCTCacheTests + +- (void)setUp +{ + self.cache = [[RCTCache alloc] init]; + self.cache.countLimit = 3; + self.cache.totalCostLimit = 100; +} + +- (void)tearDown +{ + self.cache = nil; +} + +- (void)testInsertion +{ + [self.cache setObject:@1 forKey:@"foo" cost:1]; + [self.cache setObject:@2 forKey:@"bar" cost:2]; + [self.cache setObject:@3 forKey:@"baz" cost:3]; + + XCTAssertEqual([self.cache count], 3); + XCTAssertEqual([self.cache totalCost], 6); +} + +- (void)testRemoval +{ + [self.cache setObject:@1 forKey:@"foo" cost:1]; + [self.cache setObject:@2 forKey:@"bar" cost:2]; + [self.cache setObject:@3 forKey:@"baz" cost:3]; + + [self.cache removeObjectForKey:@"bar"]; + + XCTAssertEqual([self.cache count], 2); + XCTAssertNil([self.cache objectForKey:@"bar"]); +} + +- (void)testCountEviction +{ + [self.cache setObject:@1 forKey:@"foo"]; + [self.cache setObject:@2 forKey:@"bar"]; + [self.cache setObject:@3 forKey:@"baz"]; + [self.cache setObject:@4 forKey:@"bam"]; + + XCTAssertEqual([self.cache count], 3); + XCTAssertNil([self.cache objectForKey:@"foo"]); + + [self.cache setObject:@5 forKey:@"boo"]; + + XCTAssertEqual([self.cache count], 3); + XCTAssertNil([self.cache objectForKey:@"bar"]); +} + +- (void)testCostEviction +{ + [self.cache setObject:@1 forKey:@"foo" cost:99]; + [self.cache setObject:@2 forKey:@"bar" cost:2]; + + XCTAssertEqual([self.cache count], 1); + XCTAssertEqual([self.cache totalCost], 2); + XCTAssertNil([self.cache objectForKey:@"foo"]); + + [self.cache setObject:@3 forKey:@"baz" cost:999]; + + XCTAssertEqual([self.cache count], 0); + XCTAssertEqual([self.cache totalCost], 0); +} + +- (void)testCleanup +{ + [self.cache setObject:@1 forKey:@"foo"]; + [self.cache setObject:@2 forKey:@"bar"]; + [self.cache setObject:@3 forKey:@"baz"]; + + //simulate memory warning + [self.cache cleanUpAllObjects]; + + XCTAssertEqual([self.cache count], 0); + XCTAssertEqual([self.cache totalCost], 0); +} + +- (void)testResequence +{ + [self.cache setObject:@1 forKey:@"foo"]; + [self.cache setObject:@2 forKey:@"bar"]; + [self.cache setObject:@3 forKey:@"baz"]; + + [self.cache resequence]; + + NSDictionary *innerCache = [self.cache cache]; + XCTAssertEqualObjects([innerCache[@"foo"] valueForKey:@"sequenceNumber"], @0); + XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @1); + XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @2); + + [self.cache removeObjectForKey:@"foo"]; + [self.cache resequence]; + + XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @0); + XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @1); +} + +- (void)testResequenceTrigger +{ + [self.cache setObject:@1 forKey:@"foo"]; + [self.cache setObject:@2 forKey:@"bar"]; + + //first object should now be bar with sequence number of 1 + [self.cache removeObjectForKey:@"foo"]; + + //should trigger resequence + [self.cache setSequenceNumber:NSIntegerMax]; + [self.cache setObject:@3 forKey:@"baz"]; + + NSDictionary *innerCache = [self.cache cache]; + XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @0); + XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @1); + + //first object should now be baz with sequence number of 1 + [self.cache removeObjectForKey:@"bar"]; + + //should also trigger resequence + [self.cache setSequenceNumber:NSIntegerMax]; + [self.cache objectForKey:@"baz"]; + + XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @0); +} + +- (void)testName +{ + self.cache.name = @"Hello"; + XCTAssertEqualObjects(self.cache.name, @"Hello"); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index 743cc286b..02450fab6 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m index 8897ee060..ac98f184e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m index 6ef31ff4b..0ccc13631 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import @@ -174,7 +186,7 @@ RCTAssertEqualFonts(expected, result); } { - UIFont *expected = [UIFont boldSystemFontOfSize:14]; + UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightBold]; UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar", @"fontWeight": @"bold"}]; RCTAssertEqualFonts(expected, result); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m index 188414ef9..a814fc3c9 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m @@ -1,12 +1,15 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - /** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. * - * 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. + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index afe708c5e..f396131d5 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m index 90bf824bd..a8ad81555 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index 6b788fad1..0e9290814 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -1,4 +1,16 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ #import diff --git a/Libraries/Image/RCTDownloadTaskWrapper.m b/Libraries/Image/RCTDownloadTaskWrapper.m index 21c5f6d7a..b910cb476 100644 --- a/Libraries/Image/RCTDownloadTaskWrapper.m +++ b/Libraries/Image/RCTDownloadTaskWrapper.m @@ -62,7 +62,7 @@ static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownload - (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock { - NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url completionHandler:nil]; + NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url]; task.rct_completionBlock = completionBlock; task.rct_progressBlock = progressBlock; diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index fc519c4f2..a9af87b22 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -105,16 +105,15 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); }]; NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request]; - if (cancelled) { - return; - } - - if (cachedResponse) { - runBlocks(YES, cachedResponse.data, nil); - } else { - [task resume]; - } + if (cancelled) { + return; + } + if (cachedResponse) { + runBlocks(YES, cachedResponse.data, nil); + } else { + [task resume]; + } } }); diff --git a/React/Base/RCTCache.h b/React/Base/RCTCache.h new file mode 100644 index 000000000..9a4bef4df --- /dev/null +++ b/React/Base/RCTCache.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +/** + * RCTCache is a simple LRU cache implementation, based on the API of NSCache, + * but with known, deterministic behavior. The cache will always remove items + * outside of the specified cost/count limits, and will be automatically + * cleared in the event of a memory warning. + */ +@interface RCTCache : NSCache + +/** + * The total number of objects currently resident in the cache. + */ +@property (nonatomic, readonly) NSUInteger count; + +/** + * The total cost of the objects currently resident in the cache. + */ +@property (nonatomic, readonly) NSUInteger totalCost; + +/** + * Subscripting support + */ +- (id)objectForKeyedSubscript:(id)key; +- (void)setObject:(id)obj forKeyedSubscript:(id)key; + +@end + +@protocol RCTCacheDelegate +@optional + +/** + * Should the specified object be evicted from the cache? + */ +- (BOOL)cache:(RCTCache *)cache shouldEvictObject:(id)entry; + +/** + * The specified object is about to be evicted from the cache. + */ +- (void)cache:(RCTCache *)cache willEvictObject:(id)entry; + +@end diff --git a/React/Base/RCTCache.m b/React/Base/RCTCache.m new file mode 100644 index 000000000..073e3faaa --- /dev/null +++ b/React/Base/RCTCache.m @@ -0,0 +1,326 @@ +/** + * 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. + */ + +// Adapted from https://github.com/nicklockwood/OSCache + +#import "RCTCache.h" + +#import "RCTAssert.h" + +#import +#if TARGET_OS_IPHONE +#import +#endif + +@interface RCTCacheEntry : NSObject + +@property (nonatomic, strong) NSObject *object; +@property (nonatomic, assign) NSUInteger cost; +@property (nonatomic, assign) NSInteger sequenceNumber; + +@end + +@implementation RCTCacheEntry + ++ (instancetype)entryWithObject:(id)object cost:(NSUInteger)cost sequenceNumber:(NSInteger)sequenceNumber +{ + RCTCacheEntry *entry = [[self alloc] init]; + entry.object = object; + entry.cost = cost; + entry.sequenceNumber = sequenceNumber; + return entry; +} + +@end + +@interface RCTCache_Private : NSObject + +@property (nonatomic, unsafe_unretained) id delegate; +@property (nonatomic, assign) NSUInteger countLimit; +@property (nonatomic, assign) NSUInteger totalCostLimit; +@property (nonatomic, copy) NSString *name; + +@property (nonatomic, assign) NSUInteger totalCost; +@property (nonatomic, strong) NSMutableDictionary *cache; +@property (nonatomic, assign) BOOL delegateRespondsToWillEvictObject; +@property (nonatomic, assign) BOOL delegateRespondsToShouldEvictObject; +@property (nonatomic, assign) BOOL currentlyCleaning; +@property (nonatomic, assign) NSInteger sequenceNumber; +@property (nonatomic, strong) NSLock *lock; + +@end + +@implementation RCTCache_Private + +- (instancetype)init +{ + if ((self = [super init])) + { + //create storage + _cache = [[NSMutableDictionary alloc] init]; + _lock = [[NSLock alloc] init]; + _totalCost = 0; + +#if TARGET_OS_IPHONE + + //clean up in the event of a memory warning + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanUpAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + +#endif + + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)setDelegate:(id)delegate +{ + _delegate = delegate; + _delegateRespondsToShouldEvictObject = [delegate respondsToSelector:@selector(cache:shouldEvictObject:)]; + _delegateRespondsToWillEvictObject = [delegate respondsToSelector:@selector(cache:willEvictObject:)]; +} + +- (void)setCountLimit:(NSUInteger)countLimit +{ + [_lock lock]; + _countLimit = countLimit; + [_lock unlock]; + [self cleanUp]; +} + +- (void)setTotalCostLimit:(NSUInteger)totalCostLimit +{ + [_lock lock]; + _totalCostLimit = totalCostLimit; + [_lock unlock]; + [self cleanUp]; +} + +- (NSUInteger)count +{ + return [_cache count]; +} + +- (void)cleanUp +{ + [_lock lock]; + NSUInteger maxCount = [self countLimit] ?: INT_MAX; + NSUInteger maxCost = [self totalCostLimit] ?: INT_MAX; + NSUInteger totalCount = [_cache count]; + if (totalCount > maxCount || _totalCost > maxCost) + { + //sort, oldest first + NSArray *keys = [[_cache allKeys] sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) { + RCTCacheEntry *entry1 = self.cache[key1]; + RCTCacheEntry *entry2 = self.cache[key2]; + return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber)); + }]; + + //remove oldest items until within limit + for (id key in keys) + { + if (totalCount <= maxCount && _totalCost <= maxCost) + { + break; + } + RCTCacheEntry *entry = _cache[key]; + if (!_delegateRespondsToShouldEvictObject || [self.delegate cache:(RCTCache *)self shouldEvictObject:entry]) + { + if (_delegateRespondsToWillEvictObject) + { + _currentlyCleaning = YES; + [self.delegate cache:(RCTCache *)self willEvictObject:entry]; + _currentlyCleaning = NO; + } + [_cache removeObjectForKey:key]; + _totalCost -= entry.cost; + totalCount --; + } + } + } + [_lock unlock]; +} + +- (void)cleanUpAllObjects +{ + [_lock lock]; + if (_delegateRespondsToShouldEvictObject || _delegateRespondsToWillEvictObject) + { + NSArray *keys = [_cache allKeys]; + if (_delegateRespondsToShouldEvictObject) + { + //sort, oldest first (in case we want to use that information in our eviction test) + keys = [keys sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) { + RCTCacheEntry *entry1 = self.cache[key1]; + RCTCacheEntry *entry2 = self.cache[key2]; + return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber)); + }]; + } + + //remove all items individually + for (id key in keys) + { + RCTCacheEntry *entry = _cache[key]; + if (!_delegateRespondsToShouldEvictObject || [self.delegate cache:(RCTCache *)self shouldEvictObject:entry]) + { + if (_delegateRespondsToWillEvictObject) + { + _currentlyCleaning = YES; + [self.delegate cache:(RCTCache *)self willEvictObject:entry]; + _currentlyCleaning = NO; + } + [_cache removeObjectForKey:key]; + _totalCost -= entry.cost; + } + } + } + else + { + _totalCost = 0; + [_cache removeAllObjects]; + _sequenceNumber = 0; + } + [_lock unlock]; +} + +- (void)resequence +{ + //sort, oldest first + NSArray *entries = [[_cache allValues] sortedArrayUsingComparator:^NSComparisonResult(RCTCacheEntry *entry1, RCTCacheEntry *entry2) { + return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber)); + }]; + + //renumber items + NSInteger index = 0; + for (RCTCacheEntry *entry in entries) + { + entry.sequenceNumber = index++; + } +} + +- (id)objectForKey:(id)key +{ + [_lock lock]; + RCTCacheEntry *entry = _cache[key]; + entry.sequenceNumber = _sequenceNumber++; + if (_sequenceNumber < 0) + { + [self resequence]; + } + id object = entry.object; + [_lock unlock]; + return object; +} + +- (id)objectForKeyedSubscript:(id)key +{ + return [self objectForKey:key]; +} + +- (void)setObject:(id)obj forKey:(id)key +{ + [self setObject:obj forKey:key cost:0]; +} + +- (void)setObject:(id)obj forKeyedSubscript:(id)key +{ + [self setObject:obj forKey:key cost:0]; +} + +- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g +{ + RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method."); + [_lock lock]; + _totalCost -= [_cache[key] cost]; + _totalCost += g; + _cache[key] = [RCTCacheEntry entryWithObject:obj cost:g sequenceNumber:_sequenceNumber++]; + if (_sequenceNumber < 0) + { + [self resequence]; + } + [_lock unlock]; + [self cleanUp]; +} + +- (void)removeObjectForKey:(id)key +{ + RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method."); + [_lock lock]; + _totalCost -= [_cache[key] cost]; + [_cache removeObjectForKey:key]; + [_lock unlock]; +} + +- (void)removeAllObjects +{ + RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method."); + [_lock lock]; + _totalCost = 0; + _sequenceNumber = 0; + [_cache removeAllObjects]; + [_lock unlock]; +} + +//handle unimplemented methods + +- (BOOL)isKindOfClass:(Class)cls +{ + //pretend that we're an RCTCache if anyone asks + if (cls == [RCTCache class] || cls == [NSCache class]) + { + return YES; + } + return [super isKindOfClass:cls]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + //protect against calls to unimplemented NSCache methods + NSMethodSignature *signature = [super methodSignatureForSelector:selector]; + if (!signature) + { + signature = [NSCache instanceMethodSignatureForSelector:selector]; + } + return signature; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + + [invocation invokeWithTarget:nil]; + +#pragma clang diagnostic pop +} + +@end + +@implementation RCTCache + +@dynamic count; +@dynamic totalCost; + ++ (instancetype)alloc +{ + return (RCTCache *)[RCTCache_Private alloc]; +} + +- (id)objectForKeyedSubscript:(__unused NSNumber *)key +{ + return nil; +} + +- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id)key {} + +@end diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 9ffbbaf8d..e548c3956 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -11,6 +11,7 @@ #import +#import "RCTCache.h" #import "RCTDefines.h" @implementation RCTConvert @@ -387,10 +388,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ + (UIColor *)UIColor:(id)json { // Check color cache - static NSMutableDictionary *colorCache = nil; + static RCTCache *colorCache = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - colorCache = [[NSMutableDictionary alloc] init]; + colorCache = [[RCTCache alloc] init]; + colorCache.countLimit = 1024; }); UIColor *color = colorCache[json]; if (color) { diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index b8d7f2be1..5e0434b30 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E21AA5CF210034F82E /* RCTTabBarItem.m */; }; 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; }; 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; }; + 138D6A141B53CD290074A87E /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A131B53CD290074A87E /* RCTCache.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; }; 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; }; @@ -125,6 +126,8 @@ 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = ""; }; 137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = ""; }; 137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = ""; }; + 138D6A121B53CD290074A87E /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = ""; }; + 138D6A131B53CD290074A87E /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = ""; }; 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = ""; }; 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = ""; }; 13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; @@ -407,6 +410,8 @@ 83CBBA491A601E3B00E9B192 /* Base */ = { isa = PBXGroup; children = ( + 138D6A121B53CD290074A87E /* RCTCache.h */, + 138D6A131B53CD290074A87E /* RCTCache.m */, 83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */, 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */, 14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */, @@ -599,6 +604,7 @@ 1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */, + 138D6A141B53CD290074A87E /* RCTCache.m in Sources */, 13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From 47315af0692935fb2065806c692637ab47c4096b Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Mon, 13 Jul 2015 09:37:05 -0700 Subject: [PATCH 05/12] [ReactNative] Fix SPY_MODE to show all JS->N communication. --- Libraries/Utilities/MessageQueue.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 7163b1caf..a267ddb51 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -122,10 +122,6 @@ class MessageQueue { (this._debugInfo[this._callbackID >> 5] = null); this._debugInfo[this._callbackID >> 1] = [module, method]; - if (SPY_MODE && isFinite(module)) { - console.log('JS->N : ' + this._remoteModuleTable[module] + '.' + - this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')'); - } } onFail && params.push(this._callbackID); this._callbacks[this._callbackID++] = onFail; @@ -135,6 +131,10 @@ class MessageQueue { this._queue[MODULE_IDS].push(module); this._queue[METHOD_IDS].push(method); this._queue[PARAMS].push(params); + if (__DEV__ && SPY_MODE && isFinite(module)) { + console.log('JS->N : ' + this._remoteModuleTable[module] + '.' + + this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')'); + } } __callFunction(module, method, args) { From d1b14ef0627dc04847500faa7fef75fd3c22c900 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 13 Jul 2015 09:38:53 -0700 Subject: [PATCH 06/12] [React Native] Log to ASL Summary: By default we were just writing all log messages to stderr. This also adds support for [Apple System Log](https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/asl_log.3.html) that can be viewed using standard tools. --- React/Base/RCTLog.m | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index e34502f3b..25ad49e85 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -9,6 +9,8 @@ #import "RCTLog.h" +#include + #import "RCTAssert.h" #import "RCTBridge.h" #import "RCTDefines.h" @@ -57,6 +59,25 @@ RCTLogFunction RCTDefaultLogFunction = ^( ); fprintf(stderr, "%s\n", log.UTF8String); fflush(stderr); + + int aslLevel = ASL_LEVEL_ERR; + switch(level) { + case RCTLogLevelInfo: + aslLevel = ASL_LEVEL_NOTICE; + break; + case RCTLogLevelWarning: + aslLevel = ASL_LEVEL_WARNING; + break; + case RCTLogLevelError: + aslLevel = ASL_LEVEL_ERR; + break; + case RCTLogLevelMustFix: + aslLevel = ASL_LEVEL_EMERG; + break; + default: + aslLevel = ASL_LEVEL_DEBUG; + } + asl_log(NULL, NULL, aslLevel, "%s", message.UTF8String); }; void RCTSetLogFunction(RCTLogFunction logFunction) From 90dd7a13f0a3997517f8c560bc53e213efc88049 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 13 Jul 2015 10:33:39 -0700 Subject: [PATCH 07/12] Added support for URLs pointing to files inside application home --- .../RCTConvert_NSURLTests.m | 14 +++- React/Base/RCTConvert.m | 65 ++++++++++++------- React/Base/RCTUtils.h | 6 +- React/Base/RCTUtils.m | 44 ++++++++----- 4 files changed, 83 insertions(+), 46 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m index ac98f184e..781a13d60 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_NSURLTests.m @@ -15,6 +15,7 @@ #import #import "RCTConvert.h" +#import "RCTUtils.h" @interface RCTConvert_NSURLTests : XCTestCase @@ -42,7 +43,7 @@ TEST_PATH(name, _input, [[[NSBundle mainBundle] bundlePath] stringByAppendingPat TEST_URL(basic, @"http://example.com", @"http://example.com") TEST_URL(null, (id)kCFNull, nil) -// Local files +// Resource files TEST_PATH(fileURL, @"file:///blah/hello.jsbundle", @"/blah/hello.jsbundle") TEST_BUNDLE_PATH(filePath, @"blah/hello.jsbundle", @"blah/hello.jsbundle") TEST_BUNDLE_PATH(filePathWithSpaces, @"blah blah/hello.jsbundle", @"blah blah/hello.jsbundle") @@ -50,6 +51,9 @@ TEST_BUNDLE_PATH(filePathWithEncodedSpaces, @"blah%20blah/hello.jsbundle", @"bla TEST_BUNDLE_PATH(imageAt2XPath, @"images/foo@2x.jpg", @"images/foo@2x.jpg") TEST_BUNDLE_PATH(imageFile, @"foo.jpg", @"foo.jpg") +// User documents +TEST_PATH(documentsFolder, @"~/Documents", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]) + // Remote files TEST_URL(fullURL, @"http://example.com/blah/hello.jsbundle", @"http://example.com/blah/hello.jsbundle") TEST_URL(urlWithSpaces, @"http://example.com/blah blah/foo", @"http://example.com/blah%20blah/foo") @@ -57,4 +61,12 @@ TEST_URL(urlWithEncodedSpaces, @"http://example.com/blah%20blah/foo", @"http://e TEST_URL(imageURL, @"http://example.com/foo@2x.jpg", @"http://example.com/foo@2x.jpg") TEST_URL(imageURLWithSpaces, @"http://example.com/blah foo@2x.jpg", @"http://example.com/blah%20foo@2x.jpg") +// Data URLs +- (void)testDataURL +{ + NSURL *expectedURL = RCTDataURL(@"text/plain", [@"abcde" dataUsingEncoding:NSUTF8StringEncoding]); + NSURL *testURL = [NSURL URLWithString:@"data:text/plain;base64,YWJjZGU="]; + XCTAssertEqualObjects([testURL absoluteString], [expectedURL absoluteString]); +} + @end diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index e548c3956..06e080546 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -107,7 +107,11 @@ RCT_CONVERTER(NSString *, NSString, description) // Assume that it's a local path path = [path stringByRemovingPercentEncoding]; - if (![path isAbsolutePath]) { + if ([path hasPrefix:@"~"]) { + // Path is inside user directory + path = [path stringByExpandingTildeInPath]; + } else if (![path isAbsolutePath]) { + // Assume it's a resource path path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:path]; } return [NSURL fileURLWithPath:path]; @@ -652,43 +656,54 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ return nil; } - if (RCT_DEBUG && ![json isKindOfClass:[NSString class]] && ![json isKindOfClass:[NSDictionary class]]) { - RCTLogConvertError(json, "an image"); - return nil; - } - UIImage *image; NSString *path; CGFloat scale = 0.0; if ([json isKindOfClass:[NSString class]]) { - if ([json length] == 0) { - return nil; - } path = json; - } else { + } else if ([json isKindOfClass:[NSDictionary class]]) { path = [self NSString:json[@"uri"]]; scale = [self CGFloat:json[@"scale"]]; + } else { + RCTLogConvertError(json, "an image"); } - if ([path hasPrefix:@"data:"]) { - NSURL *url = [NSURL URLWithString:path]; - NSData *imageData = [NSData dataWithContentsOfURL:url]; - image = [UIImage imageWithData:imageData]; - } else if ([path isAbsolutePath] || [path hasPrefix:@"~"]) { - image = [UIImage imageWithContentsOfFile:path.stringByExpandingTildeInPath]; - } else { - image = [UIImage imageNamed:path]; - if (!image) { - image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]]; + NSURL *URL = [self NSURL:path]; + NSString *scheme = [URL.scheme lowercaseString]; + if ([scheme isEqualToString:@"file"]) { + + if ([NSThread currentThread] == [NSThread mainThread]) { + // Image may reside inside a .car file, in which case we have no choice + // but to use +[UIImage imageNamed] - but this method isn't thread safe + image = [UIImage imageNamed:path]; } + + if (!image) { + // Attempt to load from the file system + if ([path pathExtension].length == 0) { + path = [path stringByAppendingPathExtension:@"png"]; + } + image = [UIImage imageWithContentsOfFile:path]; + } + + // We won't warn about nil images because there are legitimate cases + // where we find out if a string is an image by using this method, but + // we do enforce thread-safe API usage with the following check + if (RCT_DEBUG && !image && [UIImage imageNamed:path]) { + RCTAssertMainThread(); + } + + } else if ([scheme isEqualToString:@"data"]) { + image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; + } else { + RCTLogConvertError(json, "an image. Only local files or data URIs are supported"); } - + if (scale > 0) { - image = [UIImage imageWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; + image = [UIImage imageWithCGImage:image.CGImage + scale:scale + orientation:image.imageOrientation]; } - - // NOTE: we don't warn about nil images because there are legitimate - // case where we find out if a string is an image by using this method return image; } diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 8bdd91ccf..e21a07222 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -47,6 +47,7 @@ RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector); // Creates a standardized error object RCT_EXTERN NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData); RCT_EXTERN NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData); +RCT_EXTERN NSDictionary *RCTJSErrorFromNSError(NSError *error); // Returns YES if React is running in a test environment RCT_EXTERN BOOL RCTRunningInTestEnvironment(void); @@ -58,7 +59,8 @@ RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image); RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message); // Convert nil values to NSNull, and vice-versa -RCT_EXTERN id RCTNullIfNil(id value); RCT_EXTERN id RCTNilIfNull(id value); +RCT_EXTERN id RCTNullIfNil(id value); -RCT_EXTERN NSDictionary *RCTJSErrorFromNSError(NSError *error); +// Convert data to a Base64-encoded data URL +RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 0b7ae89c7..7fc620bc0 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -238,6 +238,27 @@ NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary return error; } +// TODO: Can we just replace RCTMakeError with this function instead? +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); +} + BOOL RCTRunningInTestEnvironment(void) { static BOOL isTestEnvironment = NO; @@ -277,23 +298,10 @@ id RCTNilIfNull(id value) return value == (id)kCFNull ? nil : value; } -// TODO: Can we just replace RCTMakeError with this function instead? -NSDictionary *RCTJSErrorFromNSError(NSError *error) +NSURL *RCTDataURL(NSString *mimeType, NSData *data) { - 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); + return [NSURL URLWithString: + [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, + [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]]; } + From d5943b0e47428fda78121a5b51c7a40a646af865 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 13 Jul 2015 10:30:34 -0700 Subject: [PATCH 08/12] [Image] Add support for tintColor to remote images Summary: Remote images now support the `tintColor` prop. Also picked nicer demo colors for the UIExplorer example. Fixes #1867 Closes https://github.com/facebook/react-native/pull/1932 Github Author: James Ide --- Examples/UIExplorer/ImageExample.js | 60 ++++++++++++++------ Libraries/Image/RCTImageDownloader.h | 1 + Libraries/Image/RCTImageDownloader.m | 5 ++ Libraries/Image/RCTNetworkImageView.h | 5 ++ Libraries/Image/RCTNetworkImageView.m | 17 +++++- Libraries/Image/RCTNetworkImageViewManager.m | 10 ++++ 6 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 60a4a5ab1..faa3267a1 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -194,23 +194,46 @@ exports.examples = [ 'pixels to the tint color.', render: function() { return ( - - - - - + + + + + + + + + It also works with downloaded images: + + + + + + + ); }, @@ -283,6 +306,9 @@ var styles = StyleSheet.create({ background: { backgroundColor: '#222222' }, + sectionText: { + marginVertical: 6, + }, nestedText: { marginLeft: 12, marginTop: 20, diff --git a/Libraries/Image/RCTImageDownloader.h b/Libraries/Image/RCTImageDownloader.h index 44b2b7369..43bb9a69d 100644 --- a/Libraries/Image/RCTImageDownloader.h +++ b/Libraries/Image/RCTImageDownloader.h @@ -38,6 +38,7 @@ typedef void (^RCTImageDownloadCancellationBlock)(void); size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode + tintColor:(UIColor *)tintColor backgroundColor:(UIColor *)backgroundColor progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTImageDownloadBlock)block; diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index a9af87b22..0f9cad198 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -131,6 +131,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode + tintColor:(UIColor *)tintColor backgroundColor:(UIColor *)backgroundColor progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTImageDownloadBlock)block @@ -173,6 +174,10 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); [blendColor setFill]; UIRectFill((CGRect){CGPointZero, destSize}); } + if (tintColor) { + image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [tintColor setFill]; + } [image drawInRect:imageRect]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); diff --git a/Libraries/Image/RCTNetworkImageView.h b/Libraries/Image/RCTNetworkImageView.h index 6cdf31216..6dd73e9aa 100644 --- a/Libraries/Image/RCTNetworkImageView.h +++ b/Libraries/Image/RCTNetworkImageView.h @@ -28,6 +28,11 @@ */ @property (nonatomic, strong) NSURL *imageURL; +/** + * Whether the image should be masked with this view's tint color. + */ +@property (nonatomic, assign) BOOL tinted; + /** * By default, changing imageURL will reset whatever existing image was present * and revert to defaultImage while the new image loads. In certain obscure cases you diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m index 8c6748f76..f8dc28157 100644 --- a/Libraries/Image/RCTNetworkImageView.m +++ b/Libraries/Image/RCTNetworkImageView.m @@ -60,6 +60,12 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) [self _updateImage]; } +- (void)setTintColor:(UIColor *)tintColor +{ + super.tintColor = tintColor; + [self _updateImage]; +} + - (void)setProgressHandlerRegistered:(BOOL)progressHandlerRegistered { _progressHandlerRegistered = progressHandlerRegistered; @@ -144,9 +150,14 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) } }]; } else { - _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() - resizeMode:self.contentMode backgroundColor:self.backgroundColor - progressBlock:progressHandler block:^(UIImage *image, NSError *error) { + _downloadToken = [_imageDownloader downloadImageForURL:imageURL + size:self.bounds.size + scale:RCTScreenScale() + resizeMode:self.contentMode + tintColor:_tinted ? self.tintColor : nil + backgroundColor:self.backgroundColor + progressBlock:progressHandler + block:^(UIImage *image, NSError *error) { if (image) { dispatch_async(dispatch_get_main_queue(), ^{ if (imageURL != self.imageURL) { diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m index 706496e34..29d990b27 100644 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ b/Libraries/Image/RCTNetworkImageViewManager.m @@ -31,6 +31,16 @@ RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage) RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) RCT_EXPORT_VIEW_PROPERTY(progressHandlerRegistered, BOOL) +RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTNetworkImageView) +{ + if (json) { + view.tinted = YES; + view.tintColor = [RCTConvert UIColor:json]; + } else { + view.tinted = defaultView.tinted; + view.tintColor = defaultView.tintColor; + } +} - (NSDictionary *)customDirectEventTypes { From 9f07b9a2b68f59715e1cf40b32e26b0b978ea882 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 13 Jul 2015 12:26:57 -0700 Subject: [PATCH 09/12] [ListView] Operate on the true scroll responder instead of the scroll component Summary: When composing scroll views, `this.refs[SCROLLVIEW_REF]` may refer to another higher-order scroll component instead of a ScrollView. This can cause issues if you expect to need it to be a ScrollView backed by an RCTScrollView. The solution is to call `getScrollResponder()` - as long as all higher-order scroll components implement this method, it will make its way down to the true ScrollView, which is what ListView wants here. Closes https://github.com/facebook/react-native/pull/1927 Github Author: James Ide --- Libraries/CustomComponents/ListView/ListView.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index f2c6c0627..d25ae38ba 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -221,7 +221,7 @@ var ListView = React.createClass({ * such as scrollTo. */ getScrollResponder: function() { - return this.refs[SCROLLVIEW_REF]; + return this.refs[SCROLLVIEW_REF].getScrollResponder(); }, setNativeProps: function(props) { @@ -399,14 +399,15 @@ var ListView = React.createClass({ */ _measureAndUpdateScrollProps: function() { + var scrollComponent = this.getScrollResponder(); RCTUIManager.measureLayout( - this.refs[SCROLLVIEW_REF].getInnerViewNode(), - React.findNodeHandle(this.refs[SCROLLVIEW_REF]), + scrollComponent.getInnerViewNode(), + React.findNodeHandle(scrollComponent), logError, this._setScrollContentHeight ); RCTUIManager.measureLayoutRelativeToParent( - React.findNodeHandle(this.refs[SCROLLVIEW_REF]), + React.findNodeHandle(scrollComponent), logError, this._setScrollVisibleHeight ); @@ -414,7 +415,7 @@ var ListView = React.createClass({ // RKScrollViewManager.calculateChildFrames not available on every platform RKScrollViewManager && RKScrollViewManager.calculateChildFrames && RKScrollViewManager.calculateChildFrames( - React.findNodeHandle(this.refs[SCROLLVIEW_REF]), + React.findNodeHandle(scrollComponent), this._updateChildFrames, ); }, From e5f7200213047f238c91743ebff4b2c56abbb256 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 13 Jul 2015 12:27:02 -0700 Subject: [PATCH 10/12] [ReactNative] Fix crash in ListView --- Libraries/CustomComponents/ListView/ListView.js | 7 ++++++- Libraries/Utilities/MessageQueue.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index d25ae38ba..496ff4bb2 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -221,7 +221,9 @@ var ListView = React.createClass({ * such as scrollTo. */ getScrollResponder: function() { - return this.refs[SCROLLVIEW_REF].getScrollResponder(); + return this.refs[SCROLLVIEW_REF] && + this.refs[SCROLLVIEW_REF].getScrollResponder && + this.refs[SCROLLVIEW_REF].getScrollResponder(); }, setNativeProps: function(props) { @@ -400,6 +402,9 @@ var ListView = React.createClass({ _measureAndUpdateScrollProps: function() { var scrollComponent = this.getScrollResponder(); + if (!scrollComponent || !scrollComponent.getInnerViewNode) { + return; + } RCTUIManager.measureLayout( scrollComponent.getInnerViewNode(), React.findNodeHandle(scrollComponent), diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index a267ddb51..98b0a9fe0 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -157,8 +157,8 @@ class MessageQueue { let callback = this._callbacks[cbID]; if (__DEV__) { let debug = this._debugInfo[cbID >> 1]; - let module = this._remoteModuleTable[debug[0]]; - let method = this._remoteMethodTable[debug[0]][debug[1]]; + let module = debug && this._remoteModuleTable[debug[0]]; + let method = debug && this._remoteMethodTable[debug[0]][debug[1]]; if (!callback) { console.error(`Callback with id ${cbID}: ${module}.${method}() not found`); } else if (SPY_MODE) { From e1ce646275cdbc9d80ce0282dbdc337db63697cb Mon Sep 17 00:00:00 2001 From: Param Aggarwal Date: Mon, 13 Jul 2015 12:44:54 -0700 Subject: [PATCH 11/12] Stray import statement - RCTTouchHandler Summary: Closes https://github.com/facebook/react-native/pull/1970 Github Author: Param Aggarwal --- React/Modules/RCTUIManager.m | 1 - 1 file changed, 1 deletion(-) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 843305eb8..f526f5f8c 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -26,7 +26,6 @@ #import "RCTScrollableProtocol.h" #import "RCTShadowView.h" #import "RCTSparseArray.h" -#import "RCTTouchHandler.h" #import "RCTUtils.h" #import "RCTView.h" #import "RCTViewManager.h" From 509e9c96e264b3d2a201f3c5678273d9e1ad5556 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 13 Jul 2015 19:46:34 -0200 Subject: [PATCH 12/12] [ReactNative] Disable flaky Layout only node scenario tests --- .../UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m index 60c238b88..d6981fba2 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m @@ -270,7 +270,7 @@ XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4])); } -- (void)testScenario1 +- (void)DISABLED_testScenario1 { RCTUIManager *uiManager = [[RCTUIManager alloc] init]; RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; @@ -354,7 +354,7 @@ } } -- (void)testScenario2 +- (void)DISABLED_testScenario2 { RCTUIManager *uiManager = [[RCTUIManager alloc] init]; RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];