Updates from Wed 24 Jun

This commit is contained in:
Alex Kotliarskyi 2015-06-24 10:49:09 -07:00
commit 2e4cbc41b0
151 changed files with 7164 additions and 4079 deletions

View File

@ -40,8 +40,9 @@ suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
[version]
0.12.0

View File

@ -77,12 +77,14 @@ var SearchScreen = React.createClass({
var apiKey = API_KEYS[this.state.queryNumber % API_KEYS.length];
if (query) {
return (
// $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined
API_URL + 'movies.json?apikey=' + apiKey + '&q=' +
encodeURIComponent(query) + '&page_limit=20&page=' + pageNumber
);
} else {
// With no query, load latest movies
return (
// $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined
API_URL + 'lists/movies/in_theaters.json?apikey=' + apiKey +
'&page_limit=20&page=' + pageNumber
);

View File

@ -600,7 +600,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
INFOPLIST_FILE = "$(SRCROOT)/iOS/Info.plist";
INFOPLIST_FILE = "iOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = SampleApp;
@ -616,7 +616,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
INFOPLIST_FILE = "$(SRCROOT)/iOS/Info.plist";
INFOPLIST_FILE = "iOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = SampleApp;

View File

@ -125,6 +125,7 @@ var Heading = React.createClass({
}
});
exports.displayName = (undefined: ?string);
exports.title = '<DatePickerIOS>';
exports.description = 'Select dates and times using the native UIDatePicker.';
exports.examples = [

View File

@ -25,6 +25,7 @@ var {
var ImageCapInsetsExample = require('./ImageCapInsetsExample');
exports.displayName = (undefined: ?string);
exports.framework = 'React';
exports.title = '<Image>';
exports.description = 'Base component for displaying different types of images.';

View File

@ -236,6 +236,7 @@ var styles = StyleSheet.create({
},
});
exports.displayName = (undefined: ?string);
exports.title = '<MapView>';
exports.description = 'Base component to display maps';
exports.examples = [

View File

@ -92,6 +92,31 @@ function newRandomRoute() {
var NavigationBarSample = React.createClass({
componentWillMount: function() {
var navigator = this.props.navigator;
var callback = (event) => {
console.log(
`NavigationBarSample : event ${event.type}`,
{
route: JSON.stringify(event.data.route),
target: event.target,
type: event.type,
}
);
};
// Observe focus change events from this component.
this._listeners = [
navigator.navigationContext.addListener('willfocus', callback),
navigator.navigationContext.addListener('didfocus', callback),
];
},
componentWillUnmount: function() {
this._listeners && this._listeners.forEach(listener => listener.remove());
},
render: function() {
return (
<Navigator

View File

@ -132,6 +132,7 @@ var TabBarExample = React.createClass({
render: function() {
return (
<Navigator
ref={this._setNavigatorRef}
style={styles.container}
initialRoute={{ message: "First Scene", }}
renderScene={this.renderScene}
@ -145,6 +146,34 @@ var TabBarExample = React.createClass({
);
},
componentWillUnmount: function() {
this._listeners && this._listeners.forEach(listener => listener.remove());
},
_setNavigatorRef: function(navigator) {
if (navigator !== this._navigator) {
this._navigator = navigator;
if (navigator) {
var callback = (event) => {
console.log(
`TabBarExample: event ${event.type}`,
{
route: JSON.stringify(event.data.route),
target: event.target,
type: event.type,
}
);
};
// Observe focus change events from the owner.
this._listeners = [
navigator.navigationContext.addListener('willfocus', callback),
navigator.navigationContext.addListener('didfocus', callback),
];
}
}
},
});
var styles = StyleSheet.create({

View File

@ -112,6 +112,7 @@ var PickerExample = React.createClass({
},
});
exports.displayName = (undefined: ?string);
exports.title = '<PickerIOS>';
exports.description = 'Render lists of selectable options with UIPickerView.';
exports.examples = [

View File

@ -60,6 +60,7 @@ var ProgressViewExample = React.createClass({
},
});
exports.displayName = (undefined: ?string);
exports.framework = 'React';
exports.title = 'ProgressViewIOS';
exports.description = 'ProgressViewIOS';

View File

@ -23,6 +23,7 @@ var {
Image
} = React;
exports.displayName = (undefined: ?string);
exports.title = '<ScrollView>';
exports.description = 'Component that enables scrolling through child components';
exports.examples = [

View File

@ -25,7 +25,7 @@ var {
var Entity = React.createClass({
render: function() {
return (
<Text style={styles.entity}>
<Text style={{fontWeight: '500', color: '#527fe4'}}>
{this.props.children}
</Text>
);
@ -34,7 +34,12 @@ var Entity = React.createClass({
var AttributeToggler = React.createClass({
getInitialState: function() {
return {fontWeight: '500', fontSize: 15};
return {fontWeight: 'bold', fontSize: 15};
},
toggleWeight: function() {
this.setState({
fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold'
});
},
increaseSize: function() {
this.setState({
@ -42,22 +47,26 @@ var AttributeToggler = React.createClass({
});
},
render: function() {
var curStyle = {fontSize: this.state.fontSize};
var curStyle = {fontWeight: this.state.fontWeight, fontSize: this.state.fontSize};
return (
<Text>
<View>
<Text style={curStyle}>
Tap the controls below to change attributes.
</Text>
<Text>
See how it will even work on{' '}
<Text style={curStyle}>
this nested text
</Text>
<Text onPress={this.increaseSize}>
{'>> Increase Size <<'}
</Text>
<Text>See how it will even work on <Text style={curStyle}>this nested text</Text></Text>
</Text>
</Text>
<Text
style={{backgroundColor: '#ffaaaa', marginTop: 5}}
onPress={this.toggleWeight}>
Toggle Weight
</Text>
<Text
style={{backgroundColor: '#aaaaff', marginTop: 5}}
onPress={this.increaseSize}>
Increase Size
</Text>
</View>
);
}
});
@ -206,6 +215,12 @@ exports.examples = [
render: function() {
return (
<View>
<Text>
auto (default) - english LTR
</Text>
<Text>
أحب اللغة العربية auto (default) - arabic RTL
</Text>
<Text style={{textAlign: 'left'}}>
left left left left left left left left left left left left left left left
</Text>
@ -282,43 +297,21 @@ exports.examples = [
description: 'backgroundColor is inherited from all types of views.',
render: function() {
return (
<View style={{backgroundColor: 'yellow'}}>
<Text>
Yellow background inherited from View parent,
<Text style={{backgroundColor: '#ffaaaa'}}>
{' '}red background,
<Text style={{backgroundColor: '#aaaaff'}}>
{' '}blue background,
<Text>
{' '}inherited blue background,
<Text style={{backgroundColor: '#aaffaa'}}>
{' '}nested green background.
</Text>
<Text style={{backgroundColor: 'yellow'}}>
Yellow container background,
<Text style={{backgroundColor: '#ffaaaa'}}>
{' '}red background,
<Text style={{backgroundColor: '#aaaaff'}}>
{' '}blue background,
<Text>
{' '}inherited blue background,
<Text style={{backgroundColor: '#aaffaa'}}>
{' '}nested green background.
</Text>
</Text>
</Text>
</Text>
</View>
);
},
}, {
title: 'containerBackgroundColor attribute',
render: function() {
return (
<View style={{backgroundColor: 'yellow'}}>
<View style={{flexDirection: 'row', position: 'absolute', height: 80}}>
<View style={{backgroundColor: '#ffaaaa', width: 140}} />
<View style={{backgroundColor: '#aaaaff', width: 140}} />
</View>
<Text style={styles.backgroundColorText}>
Default containerBackgroundColor (inherited) + backgroundColor wash
</Text>
<Text style={[
styles.backgroundColorText,
{marginBottom: 5, containerBackgroundColor: 'transparent'}]}>
{"containerBackgroundColor: 'transparent' + backgroundColor wash"}
</Text>
</View>
</Text>
);
},
}, {
@ -346,8 +339,4 @@ var styles = StyleSheet.create({
marginBottom: 0,
backgroundColor: 'rgba(100, 100, 100, 0.3)'
},
entity: {
fontWeight: '500',
color: '#527fe4',
},
});

View File

@ -133,6 +133,7 @@ var styles = StyleSheet.create({
},
});
exports.displayName = (undefined: ?string);
exports.title = '<TextInput>';
exports.description = 'Single and multi-line text inputs.';
exports.examples = [

View File

@ -26,6 +26,7 @@ var {
View,
} = React;
exports.displayName = (undefined: ?string);
exports.title = '<Touchable*> and onPress';
exports.examples = [
{

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@ -22,10 +22,10 @@
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "004D289D1AAF61C70097A701"
@ -36,10 +36,10 @@
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "143BC5941B21E3E100462512"

View File

@ -26,16 +26,12 @@
- (void)setUp
{
#if __LP64__
#error Tests should be run on 32-bit device simulators (e.g. iPhone 5)
RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
#endif
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp");
// If tests have changes, set recordMode = YES below and run the affected
// tests on an iPhone5, iOS 8.3 simulator.
_runner.recordMode = NO;
}
#pragma mark Logic Tests
@ -53,8 +49,7 @@
expectErrorBlock:nil];
}
// TODO: this seems to stall forever - figure out why
- (void)DISABLED_testTheTester_ExpectError
- (void)testTheTester_ExpectError
{
[_runner runTest:_cmd
module:@"IntegrationTestHarnessTest"
@ -91,12 +86,9 @@
- (void)testSimpleSnapshot
{
// If tests have changes, set recordMode = YES below and re-run
_runner.recordMode = NO;
[_runner runTest:_cmd module:@"SimpleSnapshotTest"];
}
- (void)testZZZ_NotInRecordMode
{
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -21,8 +21,6 @@
#import "RCTRedBox.h"
#import "RCTRootView.h"
#define TIMEOUT_SECONDS 240
@interface UIExplorerTests : XCTestCase
{
RCTTestRunner *_runner;
@ -40,52 +38,6 @@
NSString *version = [[UIDevice currentDevice] systemVersion];
RCTAssert([version isEqualToString:@"8.3"], @"Snapshot tests should be run on iOS 8.3, found %@", version);
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios");
// If tests have changes, set recordMode = YES below and run the affected
// tests on an iPhone5, iOS 8.3 simulator.
_runner.recordMode = NO;
}
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
// Make sure this test runs first because the other tests will tear out the rootView
- (void)testAAA_RootViewLoadsAndRenders
{
// TODO (t7296305) Fix and Re-Enable this UIExplorer Test
return;
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
NSString *redboxError = nil;
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) {
if ([view.accessibilityLabel isEqualToString:@"<View>"]) {
return YES;
}
return NO;
}];
}
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Couldn't find element with '<View>' text in %d seconds", TIMEOUT_SECONDS);
}
#define RCT_SNAPSHOT_TEST(name, reRecord) \
@ -102,10 +54,4 @@ RCT_SNAPSHOT_TEST(SwitchExample, NO)
RCT_SNAPSHOT_TEST(SliderExample, NO)
RCT_SNAPSHOT_TEST(TabBarExample, NO)
// Make sure this test runs last
- (void)testZZZ_NotInRecordMode
{
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
}
@end

View File

@ -53,6 +53,8 @@ var AppEventsTest = React.createClass({
}
});
AppEventsTest.displayName = 'AppEventsTest';
var styles = StyleSheet.create({
container: {
margin: 40,

View File

@ -53,15 +53,18 @@ function expectEqual(lhs, rhs, testname) {
);
}
function expectAsyncNoError(err) {
expectTrue(err === null, 'Unexpected Async error: ' + JSON.stringify(err));
function expectAsyncNoError(place, err) {
if (err instanceof Error) {
err = err.message;
}
expectTrue(err === null, 'Unexpected error in ' + place + ': ' + JSON.stringify(err));
}
function testSetAndGet() {
AsyncStorage.setItem(KEY_1, VAL_1, (err1) => {
expectAsyncNoError(err1);
expectAsyncNoError('testSetAndGet/setItem', err1);
AsyncStorage.getItem(KEY_1, (err2, result) => {
expectAsyncNoError(err2);
expectAsyncNoError('testSetAndGet/getItem', err2);
expectEqual(result, VAL_1, 'testSetAndGet setItem');
updateMessage('get(key_1) correctly returned ' + result);
runTestCase('should get null for missing key', testMissingGet);
@ -71,7 +74,7 @@ function testSetAndGet() {
function testMissingGet() {
AsyncStorage.getItem(KEY_2, (err, result) => {
expectAsyncNoError(err);
expectAsyncNoError('testMissingGet/setItem', err);
expectEqual(result, null, 'testMissingGet');
updateMessage('missing get(key_2) correctly returned ' + result);
runTestCase('check set twice results in a single key', testSetTwice);
@ -82,7 +85,7 @@ function testSetTwice() {
AsyncStorage.setItem(KEY_1, VAL_1, ()=>{
AsyncStorage.setItem(KEY_1, VAL_1, ()=>{
AsyncStorage.getItem(KEY_1, (err, result) => {
expectAsyncNoError(err);
expectAsyncNoError('testSetTwice/setItem', err);
expectEqual(result, VAL_1, 'testSetTwice');
updateMessage('setTwice worked as expected');
runTestCase('test removeItem', testRemoveItem);
@ -95,17 +98,17 @@ function testRemoveItem() {
AsyncStorage.setItem(KEY_1, VAL_1, ()=>{
AsyncStorage.setItem(KEY_2, VAL_2, ()=>{
AsyncStorage.getAllKeys((err, result) => {
expectAsyncNoError(err);
expectAsyncNoError('testRemoveItem/getAllKeys', err);
expectTrue(
result.indexOf(KEY_1) >= 0 && result.indexOf(KEY_2) >= 0,
'Missing KEY_1 or KEY_2 in ' + '(' + result + ')'
);
updateMessage('testRemoveItem - add two items');
AsyncStorage.removeItem(KEY_1, (err2) => {
expectAsyncNoError(err2);
expectAsyncNoError('testRemoveItem/removeItem', err2);
updateMessage('delete successful ');
AsyncStorage.getItem(KEY_1, (err3, result2) => {
expectAsyncNoError(err3);
expectAsyncNoError('testRemoveItem/getItem', err3);
expectEqual(
result2,
null,
@ -113,7 +116,7 @@ function testRemoveItem() {
);
updateMessage('key properly removed ');
AsyncStorage.getAllKeys((err4, result3) => {
expectAsyncNoError(err4);
expectAsyncNoError('testRemoveItem/getAllKeys', err4);
expectTrue(
result3.indexOf(KEY_1) === -1,
'Unexpected: KEY_1 present in ' + result3
@ -130,11 +133,11 @@ function testRemoveItem() {
function testMerge() {
AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => {
expectAsyncNoError(err1);
expectAsyncNoError('testMerge/setItem', err1);
AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => {
expectAsyncNoError(err2);
expectAsyncNoError('testMerge/mergeItem', err2);
AsyncStorage.getItem(KEY_MERGE, (err3, result) => {
expectAsyncNoError(err3);
expectAsyncNoError('testMerge/setItem', err3);
expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
updateMessage('objects deeply merged\nDone!');
done();
@ -173,4 +176,6 @@ var AsyncStorageTest = React.createClass({
}
});
AsyncStorageTest.displayName = 'AsyncStorageTest';
module.exports = AsyncStorageTest;

View File

@ -59,4 +59,6 @@ var IntegrationTestHarnessTest = React.createClass({
}
});
IntegrationTestHarnessTest.displayName = 'IntegrationTestHarnessTest';
module.exports = IntegrationTestHarnessTest;

View File

@ -164,4 +164,6 @@ var styles = StyleSheet.create({
},
});
LayoutEventsTest.displayName = 'LayoutEventsTest';
module.exports = LayoutEventsTest;

View File

@ -22,7 +22,7 @@ var PromiseTest = React.createClass({
Promise.all([
this.testShouldResolve(),
this.testShouldReject(),
]).then(() => RCTTestModule.finish(
]).then(() => RCTTestModule.markTestPassed(
this.shouldResolve && this.shouldReject
));
},
@ -42,9 +42,11 @@ var PromiseTest = React.createClass({
},
render() {
return <React.View />;
return <React.View />;
}
});
PromiseTest.displayName = 'PromiseTest';
module.exports = PromiseTest;

View File

@ -24,8 +24,8 @@ var SimpleSnapshotTest = React.createClass({
requestAnimationFrame(() => TestModule.verifySnapshot(this.done));
},
done() {
TestModule.markTestCompleted();
done(success) {
TestModule.markTestPassed(success);
},
render() {
@ -53,4 +53,6 @@ var styles = StyleSheet.create({
},
});
SimpleSnapshotTest.displayName = 'SimpleSnapshotTest';
module.exports = SimpleSnapshotTest;

View File

@ -152,4 +152,6 @@ var styles = StyleSheet.create({
},
});
TimersTest.displayName = 'TimersTest';
module.exports = TimersTest;

View File

@ -115,7 +115,7 @@ COMPONENTS.concat(APIS).forEach((Example) => {
// View is still blank after first RAF :\
global.requestAnimationFrame(() =>
global.requestAnimationFrame(() => TestModule.verifySnapshot(
TestModule.markTestCompleted
TestModule.markTestPassed
)
));
},

View File

@ -79,25 +79,19 @@ RCT_EXPORT_MODULE()
{
RCTBridge *_bridge;
BOOL _testMethodCalled;
dispatch_queue_t _queue;
}
@end
@implementation RCTBridgeTests
RCT_EXPORT_MODULE(TestModule)
@synthesize methodQueue = _methodQueue;
- (dispatch_queue_t)methodQueue
{
return _queue;
}
RCT_EXPORT_MODULE(TestModule)
- (void)setUp
{
[super setUp];
_queue = dispatch_queue_create("com.facebook.React.TestQueue", DISPATCH_QUEUE_SERIAL);
_bridge = [[RCTBridge alloc] initWithBundleURL:nil
moduleProvider:^{ return @[self]; }
launchOptions:nil];
@ -151,7 +145,7 @@ RCT_EXPORT_MODULE(TestModule)
[_bridge.batchedBridge _handleBuffer:buffer context:RCTGetExecutorID(executor)];
dispatch_sync(_queue, ^{
dispatch_sync(_methodQueue, ^{
// clear the queue
XCTAssertTrue(_testMethodCalled);
});

View File

@ -0,0 +1,99 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <XCTest/XCTest.h>
#import "RCTShadowView.h"
@interface RCTShadowViewTests : XCTestCase
@end
@implementation RCTShadowViewTests
// Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy.
//
// ====================================
// || header ||
// ====================================
// || || || ||
// || left || center || right ||
// || || || ||
// ====================================
// || footer ||
// ====================================
//
- (void)testApplyingLayoutRecursivelyToShadowView
{
RCTShadowView *leftView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex = 1;
}];
RCTShadowView *centerView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex = 2;
style->margin[0] = 10;
style->margin[2] = 10;
}];
RCTShadowView *rightView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex = 1;
}];
RCTShadowView *mainView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex_direction = CSS_FLEX_DIRECTION_ROW;
style->flex = 2;
style->margin[1] = 10;
style->margin[3] = 10;
}];
[mainView insertReactSubview:leftView atIndex:0];
[mainView insertReactSubview:centerView atIndex:1];
[mainView insertReactSubview:rightView atIndex:2];
RCTShadowView *headerView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex = 1;
}];
RCTShadowView *footerView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex = 1;
}];
RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
style->padding[0] = 10;
style->padding[1] = 10;
style->padding[2] = 10;
style->padding[3] = 10;
style->dimensions[0] = 440;
style->dimensions[1] = 440;
}];
[parentView insertReactSubview:headerView atIndex:0];
[parentView insertReactSubview:mainView atIndex:1];
[parentView insertReactSubview:footerView atIndex:2];
[parentView collectRootUpdatedFrames:nil parentConstraint:CGSizeZero];
XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100)));
XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200)));
XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100)));
XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200)));
XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200)));
XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200)));
}
- (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
css_style_t style = shadowView.cssNode->style;
styleBlock(&style);
shadowView.cssNode->style = style;
return shadowView;
}
@end

View File

@ -43,6 +43,7 @@ var WebViewExample = React.createClass({
backButtonEnabled: false,
forwardButtonEnabled: false,
loading: true,
scalesPageToFit: true,
};
},
@ -97,6 +98,7 @@ var WebViewExample = React.createClass({
javaScriptEnabledAndroid={true}
onNavigationStateChange={this.onNavigationStateChange}
startInLoadingState={true}
scalesPageToFit={this.state.scalesPageToFit}
/>
<View style={styles.statusBar}>
<Text style={styles.statusBarText}>{this.state.status}</Text>
@ -124,6 +126,7 @@ var WebViewExample = React.createClass({
url: navState.url,
status: navState.title,
loading: navState.loading,
scalesPageToFit: true
});
},
@ -217,6 +220,7 @@ var styles = StyleSheet.create({
},
});
exports.displayName = (undefined: ?string);
exports.title = '<WebView>';
exports.description = 'Base component to display web content';
exports.examples = [

View File

@ -235,7 +235,6 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
@try {
[view.layer setValue:toValue forKey:keypath];
NSString *animationKey = [@"RCT" stringByAppendingString:RCTJSONStringify(@{@"tag": animationTag, @"key": keypath}, nil)];
[view.layer addAnimation:animation forKey:animationKey];
if (!completionBlockSet) {
strongSelf->_callbackRegistry[animationTag] = callback;
[CATransaction setCompletionBlock:^{
@ -247,6 +246,7 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
}];
completionBlockSet = YES;
}
[view.layer addAnimation:animation forKey:animationKey];
}
@catch (NSException *exception) {
return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, toValue);

View File

@ -68,7 +68,7 @@ var AppRegistry = {
console.log(
'Running application "' + appKey + '" with appParams: ' +
JSON.stringify(appParameters) + '. ' +
'__DEV__ === ' + __DEV__ +
'__DEV__ === ' + String(__DEV__) +
', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') +
', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON')
);

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BatchedBridge
*/
'use strict';
let MessageQueue = require('MessageQueue');
let BatchedBridge = new MessageQueue(
__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig,
);
module.exports = BatchedBridge;

View File

@ -1,37 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BatchedBridge
*/
'use strict';
var BatchedBridgeFactory = require('BatchedBridgeFactory');
var MessageQueue = require('MessageQueue');
/**
* Signature that matches the native IOS modules/methods that are exposed. We
* indicate which ones accept a callback. The order of modules and methods
* within them implicitly define their numerical *ID* that will be used to
* describe method calls across the wire. This is so that memory is used
* efficiently and we do not need to copy strings in native land - or across any
* wire.
*/
var remoteModulesConfig = __fbBatchedBridgeConfig.remoteModuleConfig;
var localModulesConfig = __fbBatchedBridgeConfig.localModulesConfig;
var BatchedBridge = BatchedBridgeFactory.create(
MessageQueue,
remoteModulesConfig,
localModulesConfig
);
BatchedBridge._config = remoteModulesConfig;
module.exports = BatchedBridge;

View File

@ -1,116 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BatchedBridgeFactory
*/
'use strict';
var invariant = require('invariant');
var keyMirror = require('keyMirror');
var mapObject = require('mapObject');
var warning = require('warning');
var slice = Array.prototype.slice;
var MethodTypes = keyMirror({
remote: null,
remoteAsync: null,
local: null,
});
type ErrorData = {
message: string;
domain: string;
code: number;
nativeStackIOS?: string;
};
/**
* Creates remotely invokable modules.
*/
var BatchedBridgeFactory = {
MethodTypes: MethodTypes,
/**
* @param {MessageQueue} messageQueue Message queue that has been created with
* the `moduleConfig` (among others perhaps).
* @param {object} moduleConfig Configuration of module names/method
* names to callback types.
* @return {object} Remote representation of configured module.
*/
_createBridgedModule: function(messageQueue, moduleConfig, moduleName) {
var remoteModule = mapObject(moduleConfig.methods, function(methodConfig, memberName) {
switch (methodConfig.type) {
case MethodTypes.remoteAsync:
return function(...args) {
return new Promise((resolve, reject) => {
messageQueue.call(moduleName, memberName, args, resolve, (errorData) => {
var error = _createErrorFromErrorData(errorData);
reject(error);
});
});
};
case MethodTypes.local:
return null;
default:
return function() {
var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null;
var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null;
var hasSuccCB = typeof lastArg === 'function';
var hasErrorCB = typeof secondLastArg === 'function';
hasErrorCB && invariant(
hasSuccCB,
'Cannot have a non-function arg after a function arg.'
);
var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0);
var args = slice.call(arguments, 0, arguments.length - numCBs);
var onSucc = hasSuccCB ? lastArg : null;
var onFail = hasErrorCB ? secondLastArg : null;
return messageQueue.call(moduleName, memberName, args, onFail, onSucc);
};
}
});
for (var constName in moduleConfig.constants) {
warning(!remoteModule[constName], 'saw constant and method named %s', constName);
remoteModule[constName] = moduleConfig.constants[constName];
}
return remoteModule;
},
create: function(MessageQueue, modulesConfig, localModulesConfig) {
var messageQueue = new MessageQueue(modulesConfig, localModulesConfig);
return {
callFunction: messageQueue.callFunction.bind(messageQueue),
callFunctionReturnFlushedQueue:
messageQueue.callFunctionReturnFlushedQueue.bind(messageQueue),
invokeCallback: messageQueue.invokeCallback.bind(messageQueue),
invokeCallbackAndReturnFlushedQueue:
messageQueue.invokeCallbackAndReturnFlushedQueue.bind(messageQueue),
flushedQueue: messageQueue.flushedQueue.bind(messageQueue),
RemoteModules: mapObject(modulesConfig, this._createBridgedModule.bind(this, messageQueue)),
setLoggingEnabled: messageQueue.setLoggingEnabled.bind(messageQueue),
getLoggedOutgoingItems: messageQueue.getLoggedOutgoingItems.bind(messageQueue),
getLoggedIncomingItems: messageQueue.getLoggedIncomingItems.bind(messageQueue),
replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue),
processBatch: messageQueue.processBatch.bind(messageQueue),
};
}
};
function _createErrorFromErrorData(errorData: ErrorData): Error {
var {
message,
...extraErrorInfo,
} = errorData;
var error = new Error(message);
error.framesToPop = 1;
return Object.assign(error, extraErrorInfo);
}
module.exports = BatchedBridgeFactory;

View File

@ -71,6 +71,11 @@ var getPhotosParamChecker = createStrictShapeTypeChecker({
* Specifies filter on asset type
*/
assetType: ReactPropTypes.oneOf(ASSET_TYPE_OPTIONS),
/**
* Filter by mimetype (e.g. image/jpeg).
*/
mimeTypes: ReactPropTypes.arrayOf(ReactPropTypes.string),
});
/**

View File

@ -88,9 +88,11 @@ var styles = StyleSheet.create({
justifyContent: 'center',
},
sizeSmall: {
width: 20,
height: 20,
},
sizeLarge: {
width: 36,
height: 36,
}
});

View File

@ -42,6 +42,14 @@ var INNERVIEW = 'InnerScrollView';
* Component that wraps platform ScrollView while providing
* integration with touch locking "responder" system.
*
* Keep in mind that ScrollViews must have a bounded height in order to work,
* since they contain unbounded-height children into a bounded container (via
* a scroll interaction). In order to bound the height of a ScrollView, either
* set the height of the view directly (discouraged) or make sure all parent
* views have bounded height. Forgetting to transfer `{flex: 1}` down the
* view stack can lead to errors here, which the element inspector makes
* easy to debug.
*
* Doesn't yet support other contained responders from blocking this scroll
* view from becoming the responder.
*/

View File

@ -44,12 +44,15 @@ var AndroidTextInputAttributes = {
autoCapitalize: true,
autoCorrect: true,
autoFocus: true,
textAlign: true,
textAlignVertical: true,
keyboardType: true,
multiline: true,
password: true,
placeholder: true,
text: true,
testID: true,
underlineColorAndroid: true,
};
var viewConfigAndroid = {
@ -68,8 +71,8 @@ type Event = Object;
/**
* A foundational component for inputting text into the app via a
* keyboard. Props provide configurability for several features, such as auto-
* correction, auto-capitalization, placeholder text, and different keyboard
* keyboard. Props provide configurability for several features, such as
* auto-correction, auto-capitalization, placeholder text, and different keyboard
* types, such as a numeric keypad.
*
* The simplest use case is to plop down a `TextInput` and subscribe to the
@ -123,6 +126,19 @@ var TextInput = React.createClass({
* If true, focuses the input on componentDidMount. Default value is false.
*/
autoFocus: PropTypes.bool,
/**
* Set the position of the cursor from where editing will begin.
*/
textAlign: PropTypes.oneOf([
'start',
'center',
'end',
]),
textAlignVertical: PropTypes.oneOf([
'top',
'center',
'bottom',
]),
/**
* If false, text is not editable. Default value is true.
*/
@ -260,6 +276,10 @@ var TextInput = React.createClass({
* Used to locate this view in end-to-end tests.
*/
testID: PropTypes.string,
/**
* The color of the textInput underline. Is only supported on Android.
*/
underlineColorAndroid: PropTypes.string,
},
/**
@ -461,6 +481,10 @@ var TextInput = React.createClass({
_renderAndroid: function() {
var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize];
var textAlign =
RCTUIManager.AndroidTextInput.Constants.TextAlign[this.props.textAlign];
var textAlignVertical =
RCTUIManager.AndroidTextInput.Constants.TextAlignVertical[this.props.textAlignVertical];
var children = this.props.children;
var childCount = 0;
ReactChildren.forEach(children, () => ++childCount);
@ -477,6 +501,8 @@ var TextInput = React.createClass({
style={[this.props.style]}
autoCapitalize={autoCapitalize}
autoCorrect={this.props.autoCorrect}
textAlign={textAlign}
textAlignVertical={textAlignVertical}
keyboardType={this.props.keyboardType}
multiline={this.props.multiline}
onFocus={this._onFocus}
@ -489,6 +515,7 @@ var TextInput = React.createClass({
password={this.props.password || this.props.secureTextEntry}
placeholder={this.props.placeholder}
text={this.state.bufferedValue}
underlineColorAndroid={this.props.underlineColorAndroid}
children={children}
/>;

View File

@ -95,6 +95,11 @@ var WebView = React.createClass({
* Used for android only, JS is enabled by default for WebView on iOS
*/
javaScriptEnabledAndroid: PropTypes.bool,
/**
* Used for iOS only, sets whether the webpage scales to fit the view and the
* user can change the scale
*/
scalesPageToFit: PropTypes.bool,
},
getInitialState: function() {
@ -155,6 +160,7 @@ var WebView = React.createClass({
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
scalesPageToFit={this.props.scalesPageToFit}
/>;
return (

View File

@ -275,6 +275,10 @@ var ListView = React.createClass({
}
},
componentDidUpdate: function() {
this._measureAndUpdateScrollProps();
},
onRowHighlighted: function(sectionID, rowID) {
this.setState({highlightedRow: {sectionID, rowID}});
},
@ -368,7 +372,6 @@ var ListView = React.createClass({
if (!props.scrollEventThrottle) {
props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE;
}
return (
<ScrollView {...props}
ref={SCROLLVIEW_REF}>

View File

@ -0,0 +1,96 @@
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* Facebook, Inc. (Facebook) owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the Software). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* (Your Software). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @providesModule NavigationContext
*/
'use strict';
var NavigationEventEmitter = require('NavigationEventEmitter');
var emptyFunction = require('emptyFunction');
var invariant = require('invariant');
import type * as NavigationEvent from 'NavigationEvent';
import type * as EventSubscription from 'EventSubscription';
/**
* Class that contains the info and methods for app navigation.
*/
class NavigationContext {
_eventEmitter: ?NavigationEventEmitter;
_currentRoute: any;
constructor() {
this._eventEmitter = new NavigationEventEmitter(this);
this._currentRoute = null;
this.addListener('willfocus', this._onFocus, this);
this.addListener('didfocus', this._onFocus, this);
}
// TODO: @flow does not like this getter. Will add @flow check back once
// getter/setter is supported.
get currentRoute(): any {
return this._currentRoute;
}
addListener(
eventType: string,
listener: Function,
context: ?Object
): EventSubscription {
var emitter = this._eventEmitter;
if (emitter) {
return emitter.addListener(eventType, listener, context);
} else {
return {remove: emptyFunction};
}
}
emit(eventType: String, data: any): void {
var emitter = this._eventEmitter;
if (emitter) {
emitter.emit(eventType, data);
}
}
dispose(): void {
var emitter = this._eventEmitter;
if (emitter) {
// clean up everything.
emitter.removeAllListeners();
this._eventEmitter = null;
this._currentRoute = null;
}
}
_onFocus(event: NavigationEvent): void {
invariant(
event.data && event.data.hasOwnProperty('route'),
'didfocus event should provide route'
);
this._currentRoute = event.data.route;
}
}
module.exports = NavigationContext;

View File

@ -0,0 +1,21 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule NavigationEvent
* @flow
*/
'use strict';
class NavigationEvent {
type: String;
target: Object;
data: any;
constructor(type: String, target: Object, data: any) {
this.type = type;
this.target = target;
this.data = data;
}
}
module.exports = NavigationEvent;

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* Facebook, Inc. (Facebook) owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the Software). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* (Your Software). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @providesModule NavigationEventEmitter
* @flow
*/
'use strict';
var EventEmitter = require('EventEmitter');
var NavigationEvent = require('NavigationEvent');
type EventParams = {
eventType: String;
data: any;
};
class NavigationEventEmitter extends EventEmitter {
_emitQueue: Array<EventParams>;
_emitting: boolean;
_target: Object;
constructor(target: Object) {
super();
this._emitting = false;
this._emitQueue = [];
this._target = target;
}
emit(eventType: String, data: any): void {
if (this._emitting) {
// An event cycle that was previously created hasn't finished yet.
// Put this event cycle into the queue and will finish them later.
this._emitQueue.push({eventType, data});
return;
}
this._emitting = true;
var event = new NavigationEvent(eventType, this._target, data);
super.emit(eventType, event);
this._emitting = false;
while (this._emitQueue.length) {
var arg = this._emitQueue.shift();
this.emit(arg.eventType, arg.data);
}
}
}
module.exports = NavigationEventEmitter;

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* Facebook, Inc. (Facebook) owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the Software). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* (Your Software). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
'use strict';
jest
.dontMock('EmitterSubscription')
.dontMock('EventEmitter')
.dontMock('EventSubscriptionVendor')
.dontMock('NavigationContext')
.dontMock('NavigationEvent')
.dontMock('NavigationEventEmitter')
.dontMock('invariant');
var NavigationContext = require('NavigationContext');
describe('NavigationContext', () => {
it('defaults `currentRoute` to null', () => {
var context = new NavigationContext();
expect(context.currentRoute).toEqual(null);
});
it('updates `currentRoute`', () => {
var context = new NavigationContext();
context.emit('didfocus', {route: {name: 'a'}});
expect(context.currentRoute.name).toEqual('a');
});
});

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
*
* Facebook, Inc. (Facebook) owns all right, title and interest, including
* all intellectual property and other proprietary rights, in and to the React
* Native CustomComponents software (the Software). Subject to your
* compliance with these terms, you are hereby granted a non-exclusive,
* worldwide, royalty-free copyright license to (1) use and copy the Software;
* and (2) reproduce and distribute the Software as part of your own software
* (Your Software). Facebook reserves all rights not expressly granted to
* you in this license agreement.
*
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
'use strict';
jest
.dontMock('EmitterSubscription')
.dontMock('EventEmitter')
.dontMock('EventSubscriptionVendor')
.dontMock('NavigationEvent')
.dontMock('NavigationEventEmitter');
var NavigationEventEmitter = require('NavigationEventEmitter');
describe('NavigationEventEmitter', () => {
it('emit event', () => {
var target = {};
var emitter = new NavigationEventEmitter(target);
var focusCounter = 0;
var focusTarget;
emitter.addListener('focus', (event) => {
focusCounter++;
focusTarget = event.target;
});
emitter.emit('focus');
emitter.emit('blur');
expect(focusCounter).toBe(1);
expect(focusTarget).toBe(target);
});
it('put nested emit call in queue', () => {
var target = {};
var emitter = new NavigationEventEmitter(target);
var logs = [];
emitter.addListener('one', () => {
logs.push(1);
emitter.emit('two');
logs.push(2);
});
emitter.addListener('two', () => {
logs.push(3);
emitter.emit('three');
logs.push(4);
});
emitter.addListener('three', () => {
logs.push(5);
});
emitter.emit('one');
expect(logs).toEqual([1, 2, 3, 4, 5]);
});
});

View File

@ -30,11 +30,11 @@
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
var Dimensions = require('Dimensions');
var InteractionMixin = require('InteractionMixin');
var NavigationContext = require('NavigationContext');
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
var NavigatorNavigationBar = require('NavigatorNavigationBar');
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
var PanResponder = require('PanResponder');
var Platform = require('Platform');
var React = require('React');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
@ -203,11 +203,17 @@ var Navigator = React.createClass({
initialRouteStack: PropTypes.arrayOf(PropTypes.object),
/**
* @deprecated
* Use `navigationContext.addListener('willfocus', callback)` instead.
*
* Will emit the target route upon mounting and before each nav transition
*/
onWillFocus: PropTypes.func,
/**
* @deprecated
* Use `navigationContext.addListener('didfocus', callback)` instead.
*
* Will be called with the new route of each scene after the transition is
* complete or after the initial mounting
*/
@ -283,6 +289,9 @@ var Navigator = React.createClass({
},
componentWillMount: function() {
// TODO(t7489503): Don't need this once ES6 Class landed.
this.__defineGetter__('navigationContext', this._getNavigationContext);
this._subRouteFocus = [];
this.parentNavigator = this.props.navigator;
this._handlers = {};
@ -321,7 +330,10 @@ var Navigator = React.createClass({
},
componentWillUnmount: function() {
if (this._navigationContext) {
this._navigationContext.dispose();
this._navigationContext = null;
}
},
/**
@ -400,13 +412,11 @@ var Navigator = React.createClass({
);
} else if (this.state.activeGesture != null) {
var presentedToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
if (presentedToIndex > -1) {
this._transitionBetween(
this.state.presentedIndex,
presentedToIndex,
this.spring.getCurrentValue()
);
}
this._transitionBetween(
this.state.presentedIndex,
presentedToIndex,
this.spring.getCurrentValue()
);
}
},
@ -461,12 +471,16 @@ var Navigator = React.createClass({
},
_emitDidFocus: function(route) {
this.navigationContext.emit('didfocus', {route: route});
if (this.props.onDidFocus) {
this.props.onDidFocus(route);
}
},
_emitWillFocus: function(route) {
this.navigationContext.emit('willfocus', {route: route});
var navBar = this._navBar;
if (navBar && navBar.handleWillFocus) {
navBar.handleWillFocus(route);
@ -806,7 +820,7 @@ var Navigator = React.createClass({
this._transitionSceneStyle(fromIndex, toIndex, progress, fromIndex);
this._transitionSceneStyle(fromIndex, toIndex, progress, toIndex);
var navBar = this._navBar;
if (navBar && navBar.updateProgress) {
if (navBar && navBar.updateProgress && toIndex >= 0 && fromIndex >= 0) {
navBar.updateProgress(progress, fromIndex, toIndex);
}
},
@ -1139,6 +1153,13 @@ var Navigator = React.createClass({
</View>
);
},
_getNavigationContext: function() {
if (!this._navigationContext) {
this._navigationContext = new NavigationContext();
}
return this._navigationContext;
}
});
module.exports = Navigator;

View File

@ -178,7 +178,6 @@ var nativeOnlyProps = {
src: true,
defaultImageSrc: true,
imageTag: true,
resizeMode: true,
};
if (__DEV__) {
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ImagePickerIOS
* @flow
*/
'use strict';
var RCTImagePicker = require('NativeModules').ImagePickerIOS;
var ImagePickerIOS = {
canRecordVideos: function(callback: Function) {
return RCTImagePicker.canRecordVideos(callback);
},
canUseCamera: function(callback: Function) {
return RCTImagePicker.canUseCamera(callback);
},
openCameraDialog: function(config: Object, successCallback: Function, cancelCallback: Function) {
config = {
videoMode: false,
...config,
}
return RCTImagePicker.openCameraDialog(config, successCallback, cancelCallback);
},
openSelectDialog: function(config: Object, successCallback: Function, cancelCallback: Function) {
config = {
showImages: true,
showVideos: false,
...config,
}
return RCTImagePicker.openSelectDialog(config, successCallback, cancelCallback);
},
};
module.exports = ImagePickerIOS;

View File

@ -11,6 +11,7 @@
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; };
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
@ -39,6 +40,8 @@
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; };
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; };
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
@ -74,6 +77,8 @@
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */,
@ -155,6 +160,7 @@
buildActionMask = 2147483647;
files = (
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) 2013, 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 "RCTBridgeModule.h"
@interface RCTImagePickerManager : NSObject <RCTBridgeModule>
@end

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) 2013, 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 "RCTImagePickerManager.h"
#import "RCTRootView.h"
#import <UIKit/UIKit.h>
#import <MobileCoreServices/UTCoreTypes.h>
@interface RCTImagePickerManager ()<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@end
@implementation RCTImagePickerManager
{
NSMutableArray *_pickers;
NSMutableArray *_pickerCallbacks;
NSMutableArray *_pickerCancelCallbacks;
}
RCT_EXPORT_MODULE(ImagePickerIOS);
- (instancetype)init
{
if ((self = [super init])) {
_pickers = [[NSMutableArray alloc] init];
_pickerCallbacks = [[NSMutableArray alloc] init];
_pickerCancelCallbacks = [[NSMutableArray alloc] init];
}
return self;
}
RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback)
{
NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]);
}
RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback)
{
callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]);
}
RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config
successCallback:(RCTResponseSenderBlock)callback
cancelCallback:(RCTResponseSenderBlock)cancelCallback)
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIViewController *rootViewController = keyWindow.rootViewController;
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
if ([config[@"videoMode"] boolValue]) {
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
}
[_pickers addObject:imagePicker];
[_pickerCallbacks addObject:callback];
[_pickerCancelCallbacks addObject:cancelCallback];
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
}
RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
successCallback:(RCTResponseSenderBlock)callback
cancelCallback:(RCTResponseSenderBlock)cancelCallback)
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIViewController *rootViewController = keyWindow.rootViewController;
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
NSMutableArray *allowedTypes = [[NSMutableArray alloc] init];
if ([config[@"showImages"] boolValue]) {
[allowedTypes addObject:(NSString *)kUTTypeImage];
}
if ([config[@"showVideos"] boolValue]) {
[allowedTypes addObject:(NSString *)kUTTypeMovie];
}
imagePicker.mediaTypes = allowedTypes;
[_pickers addObject:imagePicker];
[_pickerCallbacks addObject:callback];
[_pickerCancelCallbacks addObject:cancelCallback];
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSUInteger index = [_pickers indexOfObject:picker];
RCTResponseSenderBlock callback = _pickerCallbacks[index];
[_pickers removeObjectAtIndex:index];
[_pickerCallbacks removeObjectAtIndex:index];
[_pickerCancelCallbacks removeObjectAtIndex:index];
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIViewController *rootViewController = keyWindow.rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]);
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
NSUInteger index = [_pickers indexOfObject:picker];
RCTResponseSenderBlock callback = _pickerCancelCallbacks[index];
[_pickers removeObjectAtIndex:index];
[_pickerCallbacks removeObjectAtIndex:index];
[_pickerCancelCallbacks removeObjectAtIndex:index];
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIViewController *rootViewController = keyWindow.rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
callback(@[]);
}
@end

View File

@ -26,6 +26,7 @@ class Inspector extends React.Component {
this.state = {
panelPos: 'bottom',
inspecting: true,
perfing: false,
inspected: null,
};
}
@ -59,21 +60,25 @@ class Inspector extends React.Component {
});
}
setPerfing(val: bool) {
this.setState({
perfing: val,
inspecting: false,
inspected: null,
});
}
setInspecting(val: bool) {
this.setState({
inspecting: val,
inspected: null
});
}
render() {
var panelPosition;
if (this.state.panelPos === 'bottom') {
panelPosition = {bottom: -Dimensions.get('window').height};
} else {
panelPosition = {top: 0};
}
var panelContainerStyle = (this.state.panelPos === 'bottom') ? {bottom: 0} : {top: 0};
return (
<View style={styles.container}>
<View style={styles.container} pointerEvents="box-none">
{this.state.inspecting &&
<InspectorOverlay
rootTag={this.props.rootTag}
@ -81,9 +86,11 @@ class Inspector extends React.Component {
inspectedViewTag={this.props.inspectedViewTag}
onTouchInstance={this.onTouchInstance.bind(this)}
/>}
<View style={[styles.panelContainer, panelPosition]}>
<View style={[styles.panelContainer, panelContainerStyle]}>
<InspectorPanel
inspecting={this.state.inspecting}
perfing={this.state.perfing}
setPerfing={this.setPerfing.bind(this)}
setInspecting={this.setInspecting.bind(this)}
inspected={this.state.inspected}
hierarchy={this.state.hierarchy}
@ -103,7 +110,7 @@ var styles = StyleSheet.create({
top: 0,
left: 0,
right: 0,
height: 0,
bottom: 0,
},
panelContainer: {
position: 'absolute',

View File

@ -16,6 +16,7 @@ var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
var ElementProperties = require('ElementProperties');
var PerformanceOverlay = require('PerformanceOverlay');
var TouchableHighlight = require('TouchableHighlight');
var PropTypes = React.PropTypes;
@ -44,6 +45,10 @@ class InspectorPanel extends React.Component {
setSelection={this.props.setSelection}
/>
);
} else if (this.props.perfing) {
contents = (
<PerformanceOverlay />
);
} else {
contents = (
<View style={styles.waiting}>
@ -58,7 +63,12 @@ class InspectorPanel extends React.Component {
<Button
title={'Inspect'}
pressed={this.props.inspecting}
onClick={this.props.setInspecting}/>
onClick={this.props.setInspecting}
/>
<Button title={'Perf'}
pressed={this.props.perfing}
onClick={this.props.setPerfing}
/>
</View>
</View>
);
@ -69,6 +79,8 @@ InspectorPanel.propTypes = {
inspecting: PropTypes.bool,
setInspecting: PropTypes.func,
inspected: PropTypes.object,
perfing: PropTypes.bool,
setPerfing: PropTypes.func,
};
class Button extends React.Component {

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule PerformanceOverlay
* @flow
*/
'use strict';
var PerformanceLogger = require('PerformanceLogger');
var React = require('React');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var View = require('View');
var PerformanceOverlay = React.createClass({
render: function() {
var perfLogs = PerformanceLogger.getTimespans();
var items = [];
for (var key in perfLogs) {
if (perfLogs[key].totalTime) {
items.push(
<View style={styles.row}>
<Text style={[styles.text, styles.label]}>{key}</Text>
<Text style={[styles.text, styles.totalTime]}>
{perfLogs[key].totalTime + 'ms'}
</Text>
</View>
);
}
}
return (
<View style={styles.container}>
{items}
</View>
);
}
});
var styles = StyleSheet.create({
container: {
height: 100,
paddingTop: 10,
},
label: {
flex: 1,
},
row: {
flexDirection: 'row',
paddingHorizontal: 10,
},
text: {
color: 'white',
fontSize: 12,
},
totalTime: {
paddingRight: 100,
},
});
module.exports = PerformanceOverlay;

View File

@ -24,6 +24,7 @@
// Just to make sure the JS gets packaged up.
require('RCTDeviceEventEmitter');
require('PerformanceLogger');
if (typeof GLOBAL === 'undefined') {
GLOBAL = this;

View File

@ -1,3 +1,4 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
@ -8,6 +9,7 @@
*
* @providesModule SourceMap
* @generated
* @extern
*
* This module was generated from `node_modules/source-map` by running
*

View File

@ -0,0 +1,49 @@
/**
* 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.
*/
'use strict';
require('mock-modules').autoMockOff();
var parseErrorStack = require('parseErrorStack');
function getFakeError() {
return new Error('Happy Cat');
}
describe('parseErrorStack', function() {
it('parses error stack', function() {
var stack = parseErrorStack(getFakeError());
expect(stack.length).toBeGreaterThan(0);
var firstFrame = stack[0];
expect(firstFrame.methodName).toEqual('getFakeError');
expect(firstFrame.file).toMatch(/parseErrorStack-test\.js$/);
});
it('supports framesToPop', function() {
function getWrappedError() {
var error = getFakeError();
error.framesToPop = 1;
return error;
}
// Make sure framesToPop == 1 causes it to ignore getFakeError
// stack frame
var stack = parseErrorStack(getWrappedError());
expect(stack[0].methodName).toEqual('getWrappedError');
});
it('ignores bad inputs', function() {
expect(parseErrorStack({})).toEqual([]);
expect(parseErrorStack(null)).toEqual([]);
});
});

View File

@ -18,7 +18,7 @@ var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
var SourceMapURL = require('./source-map-url');
var RCTSourceCode = NativeModules.SourceCode;
var RCTDataManager = NativeModules.DataManager;
var RCTNetworking = NativeModules.Networking;
function loadSourceMap(): Promise {
return fetchSourceMap()
@ -34,9 +34,9 @@ function fetchSourceMap(): Promise {
return Promise.reject(new Error('RCTSourceCode module is not available'));
}
if (!RCTDataManager) {
if (!RCTNetworking) {
// Used internally by fetch
return Promise.reject(new Error('RCTDataManager module is not available'));
return Promise.reject(new Error('RCTNetworking module is not available'));
}
return new Promise(RCTSourceCode.getScriptText)

View File

@ -28,6 +28,10 @@ function resolveSourceMaps(sourceMapInstance, stackFrame) {
}
function parseErrorStack(e, sourceMapInstance) {
if (!e || !e.stack) {
return [];
}
var stack = stacktraceParser.parse(e.stack);
var framesToPop = e.framesToPop || 0;

View File

@ -37,11 +37,6 @@ RCT_EXPORT_MODULE()
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (dispatch_queue_t)methodQueue
{
return dispatch_queue_create("com.facebook.React.LinkingManager", DISPATCH_QUEUE_SERIAL);
}
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication

View File

@ -9,7 +9,7 @@
/* Begin PBXBuildFile section */
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; };
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTDataManager.m */; };
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -30,8 +30,8 @@
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = "<group>"; };
58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B512061A9E6CE300147676 /* RCTDataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataManager.h; sourceTree = "<group>"; };
58B512071A9E6CE300147676 /* RCTDataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataManager.m; sourceTree = "<group>"; };
58B512061A9E6CE300147676 /* RCTNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworking.h; sourceTree = "<group>"; };
58B512071A9E6CE300147676 /* RCTNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworking.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -48,8 +48,8 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
58B512061A9E6CE300147676 /* RCTDataManager.h */,
58B512071A9E6CE300147676 /* RCTDataManager.m */,
58B512061A9E6CE300147676 /* RCTNetworking.h */,
58B512071A9E6CE300147676 /* RCTNetworking.m */,
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
1372B7351AB03E7B00659ED6 /* RCTReachability.h */,
@ -125,7 +125,7 @@
buildActionMask = 2147483647;
files = (
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */,
58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */,
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -11,7 +11,7 @@
#import "RCTBridgeModule.h"
@interface RCTDataManager : NSObject <RCTBridgeModule>
@interface RCTNetworking : NSObject <RCTBridgeModule>
@end

View File

@ -7,7 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDataManager.h"
#import "RCTNetworking.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
@ -19,7 +19,7 @@
typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
@interface RCTDataManager ()<RCTURLRequestDelegate>
@interface RCTNetworking ()<RCTURLRequestDelegate>
- (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback;
@ -30,7 +30,7 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
*/
@interface RCTHTTPFormDataHelper : NSObject
@property (nonatomic, weak) RCTDataManager *dataManager;
@property (nonatomic, weak) RCTNetworking *dataManager;
@end
@ -207,14 +207,14 @@ typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError
/**
* Bridge module that provides the JS interface to the network stack.
*/
@implementation RCTDataManager
@implementation RCTNetworking
{
NSInteger _currentRequestID;
NSMapTable *_activeRequests;
dispatch_queue_t _methodQueue;
}
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
@ -222,7 +222,6 @@ RCT_EXPORT_MODULE()
{
if ((self = [super init])) {
_currentRequestID = 0;
_methodQueue = dispatch_queue_create("com.facebook.React.RCTDataManager", DISPATCH_QUEUE_SERIAL);
_activeRequests = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory
capacity:0];
@ -230,11 +229,6 @@ RCT_EXPORT_MODULE()
return self;
}
- (dispatch_queue_t)methodQueue
{
return _methodQueue;
}
- (void)buildRequest:(NSDictionary *)query
responseSender:(RCTResponseSenderBlock)responseSender
{

View File

@ -12,7 +12,7 @@
'use strict';
var FormData = require('FormData');
var RCTDataManager = require('NativeModules').DataManager;
var RCTNetworking = require('NativeModules').Networking;
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var XMLHttpRequestBase = require('XMLHttpRequestBase');
@ -89,7 +89,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
if (data instanceof FormData) {
data = {formData: data.getParts()};
}
RCTDataManager.sendRequest(
RCTNetworking.sendRequest(
{
method,
url,
@ -103,7 +103,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
abortImpl(): void {
if (this._requestId) {
RCTDataManager.cancelRequest(this._requestId);
RCTNetworking.cancelRequest(this._requestId);
this._clearSubscriptions();
this._requestId = null;
}

View File

@ -143,7 +143,7 @@ class XMLHttpRequestBase {
return;
}
this.status = status;
this.setResponseHeaders(responseHeaders);
this.setResponseHeaders(responseHeaders || {});
this.responseText = responseText;
this.setReadyState(this.DONE);
}

View File

@ -12,6 +12,12 @@
#import "RCTBridgeModule.h"
#import "RCTDefines.h"
typedef NS_ENUM(NSInteger, RCTTestStatus) {
RCTTestStatusPending = 0,
RCTTestStatusPassed,
RCTTestStatusFailed
};
@class FBSnapshotTestController;
@interface RCTTestModule : NSObject <RCTBridgeModule>
@ -32,8 +38,8 @@
@property (nonatomic, assign) SEL testSelector;
/**
* This is typically polled while running the runloop until true.
* This is polled while running the runloop until true.
*/
@property (nonatomic, readonly, getter=isDone) BOOL done;
@property (nonatomic, readonly) RCTTestStatus status;
@end

View File

@ -51,16 +51,7 @@ RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
selector:_testSelector
identifier:_snapshotCounter[testName]
error:&error];
RCTAssert(success, @"Snapshot comparison failed: %@", error);
callback(@[]);
}];
}
RCT_EXPORT_METHOD(markTestCompleted)
{
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
_done = YES;
callback(@[@(success)]);
}];
}
@ -79,11 +70,16 @@ RCT_REMAP_METHOD(shouldReject, shouldReject_resolve:(RCTPromiseResolveBlock)reso
reject(nil);
}
RCT_EXPORT_METHOD(finish:(BOOL)success)
RCT_EXPORT_METHOD(markTestCompleted)
{
RCTAssert(success, @"RCTTestModule finished without success");
[self markTestCompleted];
[self markTestPassed:YES];
}
RCT_EXPORT_METHOD(markTestPassed:(BOOL)success)
{
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
_status = success ? RCTTestStatusPassed : RCTTestStatusFailed;
}];
}
@end

View File

@ -16,7 +16,7 @@
#import "RCTTestModule.h"
#import "RCTUtils.h"
#define TIMEOUT_SECONDS 240
#define TIMEOUT_SECONDS 60
@interface RCTBridge (RCTTestRunner)
@ -93,7 +93,7 @@ RCT_NOT_IMPLEMENTED(-init)
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
while ([date timeIntervalSinceNow] > 0 && testModule.status == RCTTestStatusPending && error == nil) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
error = [[RCTRedBox sharedInstance] currentErrorMessage];
@ -104,11 +104,12 @@ RCT_NOT_IMPLEMENTED(-init)
[[RCTRedBox sharedInstance] dismiss];
if (expectErrorBlock) {
RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched.");
} else if (error) {
RCTAssert(error == nil, @"RedBox error: %@", error);
} else {
RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
RCTAssert(error == nil, @"RedBox error: %@", error);
RCTAssert(testModule.status != RCTTestStatusPending, @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed");
}
RCTAssert(self.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
}
@end

View File

@ -251,7 +251,14 @@ ReactNativeBaseComponent.Mixin = {
this._currentElement.props, // next props
this.viewConfig.validAttributes
);
RCTUIManager.createView(tag, this.viewConfig.uiViewClassName, updatePayload);
var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
RCTUIManager.createView(
tag,
this.viewConfig.uiViewClassName,
nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null,
updatePayload
);
this._registerListenersUponCreation(this._currentElement.props);
this.initializeChildren(

View File

@ -15,10 +15,11 @@ var RCTUIManager = require('NativeModules').UIManager;
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactNativeGlobalResponderHandler = {
onChange: function(from: string, to: string) {
onChange: function(from: string, to: string, blockNativeResponder: boolean) {
if (to !== null) {
RCTUIManager.setJSResponder(
ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to)
ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to),
blockNativeResponder
);
} else {
RCTUIManager.clearJSResponder();

View File

@ -28,6 +28,7 @@ var warning = require('warning');
* unmount a component with a `rootNodeID`, then mount a new one in its place,
*/
var INITIAL_TAG_COUNT = 1;
var NATIVE_TOP_ROOT_ID_SEPARATOR = '{TOP_LEVEL}';
var ReactNativeTagHandles = {
tagsStartAt: INITIAL_TAG_COUNT,
tagCount: INITIAL_TAG_COUNT,
@ -67,7 +68,7 @@ var ReactNativeTagHandles = {
this.reactTagIsNativeTopRootID(tag),
'Expect a native root tag, instead got ', tag
);
return '.r[' + tag + ']{TOP_LEVEL}';
return '.r[' + tag + ']' + NATIVE_TOP_ROOT_ID_SEPARATOR;
},
reactTagIsNativeTopRootID: function(reactTag: number): bool {
@ -75,6 +76,17 @@ var ReactNativeTagHandles = {
return reactTag % 10 === 1;
},
getNativeTopRootIDFromNodeID: function(nodeID: ?string): ?string {
if (!nodeID) {
return null;
}
var index = nodeID.indexOf(NATIVE_TOP_ROOT_ID_SEPARATOR);
if (index === -1) {
return null;
}
return nodeID.substr(0, index + NATIVE_TOP_ROOT_ID_SEPARATOR.length);
},
/**
* Returns the native `nodeHandle` (`tag`) that was most recently *natively*
* mounted at the `rootNodeID`. Just because a React component has been

View File

@ -32,7 +32,13 @@ assign(ReactNativeTextComponent.prototype, {
mountComponent: function(rootID, transaction, context) {
this._rootNodeID = rootID;
var tag = ReactNativeTagHandles.allocateTag();
RCTUIManager.createView(tag, 'RCTRawText', {text: this._stringText});
var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
RCTUIManager.createView(
tag,
'RCTRawText',
nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null,
{text: this._stringText}
);
return {
rootNodeID: rootID,
tag: tag,

View File

@ -25,7 +25,6 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) NSUInteger numberOfLines;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) NSTextAlignment textAlign;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, assign) NSWritingDirection writingDirection;
- (void)recomputeText;

View File

@ -131,7 +131,8 @@ static css_dim_t RCTMeasure(void *context, float width)
fontSize:nil
fontWeight:nil
fontStyle:nil
letterSpacing:nil];
letterSpacing:nil
useBackgroundColor:NO];
}
- (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
@ -139,6 +140,7 @@ static css_dim_t RCTMeasure(void *context, float width)
fontWeight:(NSString *)fontWeight
fontStyle:(NSString *)fontStyle
letterSpacing:(NSNumber *)letterSpacing
useBackgroundColor:(BOOL)useBackgroundColor
{
if (![self isTextDirty] && _cachedAttributedString) {
return _cachedAttributedString;
@ -166,7 +168,7 @@ static css_dim_t RCTMeasure(void *context, float width)
for (RCTShadowView *child in [self reactSubviews]) {
if ([child isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)child;
[attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing]];
[attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing useBackgroundColor:YES]];
} else if ([child isKindOfClass:[RCTShadowRawText class]]) {
RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child;
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[shadowRawText text] ?: @""]];
@ -183,8 +185,8 @@ static css_dim_t RCTMeasure(void *context, float width)
if (_isHighlighted) {
[self _addAttribute:RCTIsHighlightedAttributeName withValue:@YES toAttributedString:attributedString];
}
if (_textBackgroundColor) {
[self _addAttribute:NSBackgroundColorAttributeName withValue:_textBackgroundColor toAttributedString:attributedString];
if (useBackgroundColor && self.backgroundColor) {
[self _addAttribute:NSBackgroundColorAttributeName withValue:self.backgroundColor toAttributedString:attributedString];
}
UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
@ -271,6 +273,12 @@ static css_dim_t RCTMeasure(void *context, float width)
[self cssNode]->children_count = 0;
}
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
super.backgroundColor = backgroundColor;
[self dirtyText];
}
#define RCT_TEXT_PROPERTY(setProp, ivar, type) \
- (void)set##setProp:(type)value; \
{ \
@ -289,7 +297,6 @@ RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize)
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *)
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
@end

View File

@ -34,6 +34,14 @@
return self;
}
- (NSString *)description
{
NSString *superDescription = super.description;
NSRange semicolonRange = [superDescription rangeOfString:@";"];
NSString *replacement = [NSString stringWithFormat:@"; reactTag: %@; text: %@", self.reactTag, self.textStorage.string];
return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement];
}
- (void)reactSetFrame:(CGRect)frame
{
// Text looks super weird if its frame is animated.

View File

@ -32,11 +32,6 @@ RCT_EXPORT_MODULE()
return [[RCTShadowText alloc] init];
}
#pragma mark - View properties
RCT_IGNORE_VIEW_PROPERTY(backgroundColor);
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
#pragma mark - Shadow properties
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
@ -50,8 +45,6 @@ RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)
RCT_REMAP_SHADOW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry

View File

@ -97,7 +97,7 @@ var Text = React.createClass({
/**
* Invoked on mount and layout changes with
*
* {nativeEvent: { layout: {x, y, width, height}}}.
* {nativeEvent: {layout: {x, y, width, height}}}.
*/
onLayout: React.PropTypes.func,
},

View File

@ -25,8 +25,7 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
fontStyle: ReactPropTypes.oneOf(['normal', 'italic']),
lineHeight: ReactPropTypes.number,
color: ReactPropTypes.string,
containerBackgroundColor: ReactPropTypes.string,
// NOTE: "justify" is supported only on iOS
// NOTE: 'justify is supported only on iOS
textAlign: ReactPropTypes.oneOf(
['auto' /*default*/, 'left', 'right', 'center', 'justify']
),

View File

@ -14,7 +14,7 @@
var GLOBAL = GLOBAL || this;
var BridgeProfiling = {
profile(profileName?: string, args?: any) {
profile(profileName?: any, args?: any) {
if (GLOBAL.__BridgeProfilingIsProfiling) {
if (args) {
try {
@ -23,6 +23,8 @@ var BridgeProfiling = {
args = err.message;
}
}
profileName = typeof profileName === 'function' ?
profileName() : profileName;
console.profile(profileName, args);
}
},

View File

@ -7,541 +7,241 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule MessageQueue
* @flow
*/
/*eslint no-bitwise: 0*/
'use strict';
var ErrorUtils = require('ErrorUtils');
var ReactUpdates = require('ReactUpdates');
let BridgeProfiling = require('BridgeProfiling');
let ErrorUtils = require('ErrorUtils');
let JSTimersExecution = require('JSTimersExecution');
let ReactUpdates = require('ReactUpdates');
var invariant = require('invariant');
var warning = require('warning');
let invariant = require('invariant');
let keyMirror = require('keyMirror');
let stringifySafe = require('stringifySafe');
var BridgeProfiling = require('BridgeProfiling');
var JSTimersExecution = require('JSTimersExecution');
let MODULE_IDS = 0;
let METHOD_IDS = 1;
let PARAMS = 2;
var INTERNAL_ERROR = 'Error in MessageQueue implementation';
let MethodTypes = keyMirror({
local: null,
remote: null,
remoteAsync: null,
});
// Prints all bridge traffic to console.log
var DEBUG_SPY_MODE = false;
type ModulesConfig = {
[key:string]: {
moduleID: number;
methods: {[key:string]: {
methodID: number;
}};
var guard = (fn) => {
try {
fn();
} catch (error) {
ErrorUtils.reportFatalError(error);
}
};
class MessageQueue {
constructor(remoteModules, localModules, customRequire) {
this.RemoteModules = {};
this._require = customRequire || require;
this._queue = [[],[],[]];
this._moduleTable = {};
this._methodTable = {};
this._callbacks = [];
this._callbackID = 0;
[
'processBatch',
'invokeCallbackAndReturnFlushedQueue',
'callFunctionReturnFlushedQueue',
'flushedQueue',
].forEach((fn) => this[fn] = this[fn].bind(this));
this._genModules(remoteModules);
localModules && this._genLookupTables(
localModules, this._moduleTable, this._methodTable);
if (__DEV__) {
this._debugInfo = {};
this._remoteModuleTable = {};
this._remoteMethodTable = {};
this._genLookupTables(
remoteModules, this._remoteModuleTable, this._remoteMethodTable);
}
}
/**
* Public APIs
*/
processBatch(batch) {
ReactUpdates.batchedUpdates(() => {
batch.forEach((call) => {
let method = call.method === 'callFunctionReturnFlushedQueue' ?
'__callFunction' : '__invokeCallback';
guard(() => this[method].apply(this, call.args));
});
BridgeProfiling.profile('ReactUpdates.batchedUpdates()');
});
BridgeProfiling.profileEnd();
return this.flushedQueue();
}
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => this.__callFunction(module, method, args));
return this.flushedQueue();
}
invokeCallbackAndReturnFlushedQueue(cbID, args) {
guard(() => this.__invokeCallback(cbID, args));
return this.flushedQueue();
}
flushedQueue() {
BridgeProfiling.profile('JSTimersExecution.callImmediates()');
guard(() => JSTimersExecution.callImmediates());
BridgeProfiling.profileEnd();
let queue = this._queue;
this._queue = [[],[],[]];
return queue[0].length ? queue : null;
}
/**
* "Private" methods
*/
__nativeCall(module, method, params, onFail, onSucc) {
if (onFail || onSucc) {
if (__DEV__) {
// eventually delete old debug info
(this._callbackID > (1 << 5)) &&
(this._debugInfo[this._callbackID >> 5] = null);
this._debugInfo[this._callbackID >> 1] = [module, method];
}
onFail && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onFail;
onSucc && params.push(this._callbackID);
this._callbacks[this._callbackID++] = onSucc;
}
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
}
__callFunction(module, method, args) {
BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`);
if (isFinite(module)) {
method = this._methodTable[module][method];
module = this._moduleTable[module];
}
module = this._require(module);
module[method].apply(module, args);
BridgeProfiling.profileEnd();
}
__invokeCallback(cbID, args) {
BridgeProfiling.profile(
() => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`);
let callback = this._callbacks[cbID];
if (__DEV__ && !callback) {
let debug = this._debugInfo[cbID >> 1];
let module = this._remoteModuleTable[debug[0]];
let method = this._remoteMethodTable[debug[0]][debug[1]];
console.error(`Callback with id ${cbID}: ${module}.${method}() not found`);
}
this._callbacks[cbID & ~1] = null;
this._callbacks[cbID | 1] = null;
callback.apply(null, args);
BridgeProfiling.profileEnd();
}
/**
* Private helper methods
*/
_genLookupTables(localModules, moduleTable, methodTable) {
let moduleNames = Object.keys(localModules);
for (var i = 0, l = moduleNames.length; i < l; i++) {
let moduleName = moduleNames[i];
let methods = localModules[moduleName].methods;
let moduleID = localModules[moduleName].moduleID;
moduleTable[moduleID] = moduleName;
methodTable[moduleID] = {};
let methodNames = Object.keys(methods);
for (var j = 0, k = methodNames.length; j < k; j++) {
let methodName = methodNames[j];
let methodConfig = methods[methodName];
methodTable[moduleID][methodConfig.methodID] = methodName;
}
}
}
_genModules(remoteModules) {
let moduleNames = Object.keys(remoteModules);
for (var i = 0, l = moduleNames.length; i < l; i++) {
let moduleName = moduleNames[i];
let moduleConfig = remoteModules[moduleName];
this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
}
}
_genModule(module, moduleConfig) {
let methodNames = Object.keys(moduleConfig.methods);
for (var i = 0, l = methodNames.length; i < l; i++) {
let methodName = methodNames[i];
let methodConfig = moduleConfig.methods[methodName];
module[methodName] = this._genMethod(
moduleConfig.moduleID, methodConfig.methodID, methodConfig.type);
}
Object.assign(module, moduleConfig.constants);
return module;
}
_genMethod(module, method, type) {
if (type === MethodTypes.local) {
return null;
}
let self = this;
if (type === MethodTypes.remoteAsync) {
return function(...args) {
return new Promise((resolve, reject) => {
self.__nativeCall(module, method, args, resolve, (errorData) => {
var error = createErrorFromErrorData(errorData);
reject(error);
});
});
};
} else {
return function(...args) {
let lastArg = args.length > 0 ? args[args.length - 1] : null;
let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
let hasSuccCB = typeof lastArg === 'function';
let hasErrorCB = typeof secondLastArg === 'function';
hasErrorCB && invariant(
hasSuccCB,
'Cannot have a non-function arg after a function arg.'
);
let numCBs = hasSuccCB + hasErrorCB;
let onSucc = hasSuccCB ? lastArg : null;
let onFail = hasErrorCB ? secondLastArg : null;
args = args.slice(0, args.length - numCBs);
return self.__nativeCall(module, method, args, onFail, onSucc);
};
}
}
}
type NameToID = {[key:string]: number}
type IDToName = {[key:number]: string}
function createErrorFromErrorData(errorData: ErrorData): Error {
var {
message,
...extraErrorInfo,
} = errorData;
var error = new Error(message);
error.framesToPop = 1;
return Object.assign(error, extraErrorInfo);
}
/**
* So as not to confuse static build system.
*/
var requireFunc = require;
/**
* @param {Object!} module Module instance, must be loaded.
* @param {string} methodName Name of method in `module`.
* @param {array<*>} params Arguments to method.
* @returns {*} Return value of method invocation.
*/
var jsCall = function(module, methodName, params) {
return module[methodName].apply(module, params);
};
/**
* A utility for aggregating "work" to be done, and potentially transferring
* that work to another thread. Each instance of `MessageQueue` has the notion
* of a "target" thread - the thread that the work will be sent to.
*
* TODO: Long running callback results, and streaming callback results (ability
* for a callback to be invoked multiple times).
*
* @param {object} moduleNameToID Used to translate module/method names into
* efficient numeric IDs.
* @class MessageQueue
*/
var MessageQueue = function(
remoteModulesConfig: ModulesConfig,
localModulesConfig: ModulesConfig,
customRequire: (id: string) => any
) {
this._requireFunc = customRequire || requireFunc;
this._initBookeeping();
this._initNamingMap(remoteModulesConfig, localModulesConfig);
};
// REQUEST: Parallell arrays:
var REQUEST_MODULE_IDS = 0;
var REQUEST_METHOD_IDS = 1;
var REQUEST_PARAMSS = 2;
// RESPONSE: Parallell arrays:
var RESPONSE_CBIDS = 3;
var RESPONSE_RETURN_VALUES = 4;
var applyWithErrorReporter = function(fun: Function, context: ?any, args: ?any) {
try {
return fun.apply(context, args);
} catch (e) {
ErrorUtils.reportFatalError(e);
}
};
/**
* Utility to catch errors and prevent having to bind, or execute a bound
* function, while catching errors in a process and returning a resulting
* return value. This ensures that even if a process fails, we can still return
* *some* values (from `_flushedQueueUnguarded` for example). Glorified
* try/catch/finally that invokes the global `onerror`.
*
* @param {function} operation Function to execute, likely populates the
* message buffer.
* @param {Array<*>} operationArguments Arguments passed to `operation`.
* @param {function} getReturnValue Returns a return value - will be invoked
* even if the `operation` fails half way through completing its task.
* @return {object} Return value returned from `getReturnValue`.
*/
var guardReturn = function(operation, operationArguments, getReturnValue, context) {
if (operation) {
applyWithErrorReporter(operation, context, operationArguments);
}
if (getReturnValue) {
return applyWithErrorReporter(getReturnValue, context, null);
}
return null;
};
/**
* Bookkeeping logic for callbackIDs. We ensure that success and error
* callbacks are numerically adjacent.
*
* We could have also stored the association between success cbID and errorCBID
* in a map without relying on this adjacency, but the bookkeeping here avoids
* an additional two maps to associate in each direction, and avoids growing
* dictionaries (new fields). Instead, we compute pairs of callback IDs, by
* populating the `res` argument to `allocateCallbackIDs` (in conjunction with
* pooling). Behind this bookeeping API, we ensure that error and success
* callback IDs are always adjacent so that when one is invoked, we always know
* how to free the memory of the other. By using this API, it is impossible to
* create malformed callbackIDs that are not adjacent.
*/
var createBookkeeping = function() {
return {
/**
* Incrementing callback ID. Must start at 1 - otherwise converted null
* values which become zero are not distinguishable from a GUID of zero.
*/
GUID: 1,
errorCallbackIDForSuccessCallbackID: function(successID) {
return successID + 1;
},
successCallbackIDForErrorCallbackID: function(errorID) {
return errorID - 1;
},
allocateCallbackIDs: function(res) {
res.successCallbackID = this.GUID++;
res.errorCallbackID = this.GUID++;
},
isSuccessCallback: function(id) {
return id % 2 === 1;
}
};
};
var MessageQueueMixin = {
/**
* Creates an efficient wire protocol for communicating across a bridge.
* Avoids allocating strings.
*
* @param {object} remoteModulesConfig Configuration of modules and their
* methods.
*/
_initNamingMap: function(
remoteModulesConfig: ModulesConfig,
localModulesConfig: ModulesConfig
) {
this._remoteModuleNameToModuleID = {};
this._remoteModuleIDToModuleName = {}; // Reverse
this._remoteModuleNameToMethodNameToID = {};
this._remoteModuleNameToMethodIDToName = {}; // Reverse
this._localModuleNameToModuleID = {};
this._localModuleIDToModuleName = {}; // Reverse
this._localModuleNameToMethodNameToID = {};
this._localModuleNameToMethodIDToName = {}; // Reverse
function fillMappings(
modulesConfig: ModulesConfig,
moduleNameToModuleID: NameToID,
moduleIDToModuleName: IDToName,
moduleNameToMethodNameToID: {[key:string]: NameToID},
moduleNameToMethodIDToName: {[key:string]: IDToName}
) {
for (var moduleName in modulesConfig) {
var moduleConfig = modulesConfig[moduleName];
var moduleID = moduleConfig.moduleID;
moduleNameToModuleID[moduleName] = moduleID;
moduleIDToModuleName[moduleID] = moduleName; // Reverse
moduleNameToMethodNameToID[moduleName] = {};
moduleNameToMethodIDToName[moduleName] = {}; // Reverse
var methods = moduleConfig.methods;
for (var methodName in methods) {
var methodID = methods[methodName].methodID;
moduleNameToMethodNameToID[moduleName][methodName] =
methodID;
moduleNameToMethodIDToName[moduleName][methodID] =
methodName; // Reverse
}
}
}
fillMappings(
remoteModulesConfig,
this._remoteModuleNameToModuleID,
this._remoteModuleIDToModuleName,
this._remoteModuleNameToMethodNameToID,
this._remoteModuleNameToMethodIDToName
);
fillMappings(
localModulesConfig,
this._localModuleNameToModuleID,
this._localModuleIDToModuleName,
this._localModuleNameToMethodNameToID,
this._localModuleNameToMethodIDToName
);
},
_initBookeeping: function() {
this._POOLED_CBIDS = {errorCallbackID: null, successCallbackID: null};
this._bookkeeping = createBookkeeping();
/**
* Stores callbacks so that we may simulate asynchronous return values from
* other threads. Remote invocations in other threads can pass return values
* back asynchronously to the requesting thread.
*/
this._threadLocalCallbacksByID = [];
this._threadLocalScopesByID = [];
/**
* Memory efficient parallel arrays. Each index cuts through the three
* arrays and forms a remote invocation of methodName(params) whos return
* value will be reported back to the other thread by way of the
* corresponding id in cbIDs. Each entry (A-D in the graphic below),
* represents a work item of the following form:
* - moduleID: ID of module to invoke method from.
* - methodID: ID of method in module to invoke.
* - params: List of params to pass to method.
* - cbID: ID to respond back to originating thread with.
*
* TODO: We can make this even more efficient (memory) by creating a single
* array, that is always pushed `n` elements as a time.
*/
this._outgoingItems = [
/*REQUEST_MODULE_IDS: */ [/* +-+ +-+ +-+ +-+ */],
/*REQUEST_METHOD_IDS: */ [/* |A| |B| |C| |D| */],
/*REQUEST_PARAMSS: */ [/* |-| |-| |-| |-| */],
/*RESPONSE_CBIDS: */ [/* +-+ +-+ +-+ +-+ */],
/* |E| |F| |G| |H| */
/*RESPONSE_RETURN_VALUES: */ [/* +-+ +-+ +-+ +-+ */]
];
/**
* Used to allow returning the buffer, while at the same time clearing it in
* a memory efficient manner.
*/
this._outgoingItemsSwap = [[], [], [], [], []];
},
invokeCallback: function(cbID, args) {
return guardReturn(this._invokeCallback, [cbID, args], null, this);
},
_invokeCallback: function(cbID, args) {
try {
var cb = this._threadLocalCallbacksByID[cbID];
var scope = this._threadLocalScopesByID[cbID];
warning(
cb,
'Cannot find callback with CBID %s. Native module may have invoked ' +
'both the success callback and the error callback.',
cbID
);
if (DEBUG_SPY_MODE) {
console.log('N->JS: Callback#' + cbID + '(' + JSON.stringify(args) + ')');
}
BridgeProfiling.profile('Callback#' + cbID + '(' + JSON.stringify(args) + ')');
cb.apply(scope, args);
BridgeProfiling.profileEnd();
} catch(ie_requires_catch) {
throw ie_requires_catch;
} finally {
// Clear out the memory regardless of success or failure.
this._freeResourcesForCallbackID(cbID);
}
},
invokeCallbackAndReturnFlushedQueue: function(cbID, args) {
if (this._enableLogging) {
this._loggedIncomingItems.push([new Date().getTime(), cbID, args]);
}
return guardReturn(
this._invokeCallback,
[cbID, args],
this._flushedQueueUnguarded,
this
);
},
callFunction: function(moduleID, methodID, params) {
return guardReturn(this._callFunction, [moduleID, methodID, params], null, this);
},
_callFunction: function(moduleName, methodName, params) {
if (isFinite(moduleName)) {
moduleName = this._localModuleIDToModuleName[moduleName];
methodName = this._localModuleNameToMethodIDToName[moduleName][methodName];
}
if (DEBUG_SPY_MODE) {
console.log(
'N->JS: ' + moduleName + '.' + methodName +
'(' + JSON.stringify(params) + ')');
}
BridgeProfiling.profile(moduleName + '.' + methodName + '(' + JSON.stringify(params) + ')');
var ret = jsCall(this._requireFunc(moduleName), methodName, params);
BridgeProfiling.profileEnd();
return ret;
},
callFunctionReturnFlushedQueue: function(moduleID, methodID, params) {
if (this._enableLogging) {
this._loggedIncomingItems.push([new Date().getTime(), moduleID, methodID, params]);
}
return guardReturn(
this._callFunction,
[moduleID, methodID, params],
this._flushedQueueUnguarded,
this
);
},
processBatch: function(batch) {
var self = this;
BridgeProfiling.profile('MessageQueue.processBatch()');
var flushedQueue = guardReturn(function () {
ReactUpdates.batchedUpdates(function() {
batch.forEach(function(call) {
invariant(
call.module === 'BatchedBridge',
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self._callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self._invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
});
BridgeProfiling.profile('React.batchedUpdates()');
});
BridgeProfiling.profileEnd();
}, null, this._flushedQueueUnguarded, this);
BridgeProfiling.profileEnd();
return flushedQueue;
},
setLoggingEnabled: function(enabled) {
this._enableLogging = enabled;
this._loggedIncomingItems = [];
this._loggedOutgoingItems = [[], [], [], [], []];
},
getLoggedIncomingItems: function() {
return this._loggedIncomingItems;
},
getLoggedOutgoingItems: function() {
return this._loggedOutgoingItems;
},
replayPreviousLog: function(previousLog) {
this._outgoingItems = previousLog;
},
/**
* Simple helpers for clearing the queues. This doesn't handle the fact that
* memory in the current buffer is leaked until the next frame or update - but
* that will typically be on the order of < 500ms.
*/
_swapAndReinitializeBuffer: function() {
// Outgoing requests
var currentOutgoingItems = this._outgoingItems;
var nextOutgoingItems = this._outgoingItemsSwap;
nextOutgoingItems[REQUEST_MODULE_IDS].length = 0;
nextOutgoingItems[REQUEST_METHOD_IDS].length = 0;
nextOutgoingItems[REQUEST_PARAMSS].length = 0;
// Outgoing responses
nextOutgoingItems[RESPONSE_CBIDS].length = 0;
nextOutgoingItems[RESPONSE_RETURN_VALUES].length = 0;
this._outgoingItemsSwap = currentOutgoingItems;
this._outgoingItems = nextOutgoingItems;
},
/**
* @param {string} moduleID JS module name.
* @param {methodName} methodName Method in module to invoke.
* @param {array<*>?} params Array representing arguments to method.
* @param {string} cbID Unique ID to pass back in potential response.
*/
_pushRequestToOutgoingItems: function(moduleID, methodName, params) {
this._outgoingItems[REQUEST_MODULE_IDS].push(moduleID);
this._outgoingItems[REQUEST_METHOD_IDS].push(methodName);
this._outgoingItems[REQUEST_PARAMSS].push(params);
if (this._enableLogging) {
this._loggedOutgoingItems[REQUEST_MODULE_IDS].push(moduleID);
this._loggedOutgoingItems[REQUEST_METHOD_IDS].push(methodName);
this._loggedOutgoingItems[REQUEST_PARAMSS].push(params);
}
},
/**
* @param {string} cbID Unique ID that other side of bridge has remembered.
* @param {*} returnValue Return value to pass to callback on other side of
* bridge.
*/
_pushResponseToOutgoingItems: function(cbID, returnValue) {
this._outgoingItems[RESPONSE_CBIDS].push(cbID);
this._outgoingItems[RESPONSE_RETURN_VALUES].push(returnValue);
},
_freeResourcesForCallbackID: function(cbID) {
var correspondingCBID = this._bookkeeping.isSuccessCallback(cbID) ?
this._bookkeeping.errorCallbackIDForSuccessCallbackID(cbID) :
this._bookkeeping.successCallbackIDForErrorCallbackID(cbID);
this._threadLocalCallbacksByID[cbID] = null;
this._threadLocalScopesByID[cbID] = null;
if (this._threadLocalCallbacksByID[correspondingCBID]) {
this._threadLocalCallbacksByID[correspondingCBID] = null;
this._threadLocalScopesByID[correspondingCBID] = null;
}
},
/**
* @param {Function} onFail Function to store in current thread for later
* lookup, when request fails.
* @param {Function} onSucc Function to store in current thread for later
* lookup, when request succeeds.
* @param {Object?=} scope Scope to invoke `cb` with.
* @param {Object?=} res Resulting callback ids. Use `this._POOLED_CBIDS`.
*/
_storeCallbacksInCurrentThread: function(onFail, onSucc, scope) {
invariant(onFail || onSucc, INTERNAL_ERROR);
this._bookkeeping.allocateCallbackIDs(this._POOLED_CBIDS);
var succCBID = this._POOLED_CBIDS.successCallbackID;
var errorCBID = this._POOLED_CBIDS.errorCallbackID;
this._threadLocalCallbacksByID[errorCBID] = onFail;
this._threadLocalCallbacksByID[succCBID] = onSucc;
this._threadLocalScopesByID[errorCBID] = scope;
this._threadLocalScopesByID[succCBID] = scope;
},
/**
* IMPORTANT: There is possibly a timing issue with this form of flushing. We
* are currently not seeing any problems but the potential issue to look out
* for is:
* - While flushing this._outgoingItems contains the work for the other thread
* to perform.
* - To mitigate this, we never allow enqueueing messages if the queue is
* already reserved - as long as it is reserved, it could be in the midst of
* a flush.
*
* If this ever occurs we can easily eliminate the race condition. We can
* completely solve any ambiguity by sending messages such that we'll never
* try to reserve the queue when already reserved. Here's the pseudocode:
*
* var defensiveCopy = efficientDefensiveCopy(this._outgoingItems);
* this._swapAndReinitializeBuffer();
*/
flushedQueue: function() {
return guardReturn(null, null, this._flushedQueueUnguarded, this);
},
_flushedQueueUnguarded: function() {
BridgeProfiling.profile('JSTimersExecution.callImmediates()');
// Call the functions registered via setImmediate
JSTimersExecution.callImmediates();
BridgeProfiling.profileEnd();
var currentOutgoingItems = this._outgoingItems;
this._swapAndReinitializeBuffer();
var ret = currentOutgoingItems[REQUEST_MODULE_IDS].length ||
currentOutgoingItems[RESPONSE_RETURN_VALUES].length ? currentOutgoingItems : null;
if (DEBUG_SPY_MODE && ret) {
for (var i = 0; i < currentOutgoingItems[0].length; i++) {
var moduleName = this._remoteModuleIDToModuleName[currentOutgoingItems[0][i]];
var methodName =
this._remoteModuleNameToMethodIDToName[moduleName][currentOutgoingItems[1][i]];
console.log(
'JS->N: ' + moduleName + '.' + methodName +
'(' + JSON.stringify(currentOutgoingItems[2][i]) + ')');
}
}
return ret;
},
call: function(moduleName, methodName, params, onFail, onSucc, scope) {
invariant(
(!onFail || typeof onFail === 'function') &&
(!onSucc || typeof onSucc === 'function'),
'Callbacks must be functions'
);
// Store callback _before_ sending the request, just in case the MailBox
// returns the response in a blocking manner.
if (onFail || onSucc) {
this._storeCallbacksInCurrentThread(onFail, onSucc, scope, this._POOLED_CBIDS);
onFail && params.push(this._POOLED_CBIDS.errorCallbackID);
onSucc && params.push(this._POOLED_CBIDS.successCallbackID);
}
var moduleID = this._remoteModuleNameToModuleID[moduleName];
if (moduleID === undefined || moduleID === null) {
throw new Error('Unrecognized module name:' + moduleName);
}
var methodID = this._remoteModuleNameToMethodNameToID[moduleName][methodName];
if (methodID === undefined || moduleID === null) {
throw new Error('Unrecognized method name:' + methodName);
}
this._pushRequestToOutgoingItems(moduleID, methodID, params);
},
__numPendingCallbacksOnlyUseMeInTestCases: function() {
var callbacks = this._threadLocalCallbacksByID;
var total = 0;
for (var i = 0; i < callbacks.length; i++) {
if (callbacks[i]) {
total++;
}
}
return total;
}
};
Object.assign(MessageQueue.prototype, MessageQueueMixin);
module.exports = MessageQueue;

View File

@ -0,0 +1,96 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule PerformanceLogger
*/
'use strict';
var performanceNow = require('performanceNow');
var timespans = {};
/**
* This is meant to collect and log performance data in production, which means
* it needs to have minimal overhead.
*/
var PerformanceLogger = {
addTimespan(key, lengthInMs, description) {
if (timespans[key]) {
if (__DEV__) {
console.log(
'PerformanceLogger: Attempting to add a timespan that already exists'
);
}
return;
}
timespans[key] = {
description: description,
totalTime: lengthInMs,
};
},
startTimespan(key, description) {
if (timespans[key]) {
if (__DEV__) {
console.log(
'PerformanceLogger: Attempting to start a timespan that already exists'
);
}
return;
}
timespans[key] = {
description: description,
startTime: performanceNow(),
};
},
stopTimespan(key) {
if (!timespans[key] || !timespans[key].startTime) {
if (__DEV__) {
console.log(
'PerformanceLogger: Attempting to end a timespan that has not started'
);
}
return;
}
timespans[key].endTime = performanceNow();
timespans[key].totalTime =
timespans[key].endTime - timespans[key].startTime;
},
clearTimespans() {
timespans = {};
},
getTimespans() {
return timespans;
},
logTimespans() {
for (var key in timespans) {
console.log(key + ': ' + timespans[key].totalTime + 'ms');
}
},
addTimespans(newTimespans, labels) {
for (var i = 0, l = newTimespans.length; i < l; i += 2) {
var label = labels[i / 2];
PerformanceLogger.addTimespan(
label,
(newTimespans[i + 1] - newTimespans[i]),
label
);
}
}
};
module.exports = PerformanceLogger;

View File

@ -0,0 +1,141 @@
/**
* 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.
*/
'use strict';
jest.setMock('ReactUpdates', {
batchedUpdates: fn => fn()
});
jest.dontMock('MessageQueue');
jest.dontMock('keyMirror');
var MessageQueue = require('MessageQueue');
let MODULE_IDS = 0;
let METHOD_IDS = 1;
let PARAMS = 2;
let TestModule = {
testHook1(){}, testHook2(){},
};
let customRequire = (moduleName) => TestModule;
let assertQueue = (flushedQueue, index, moduleID, methodID, params) => {
expect(flushedQueue[MODULE_IDS][index]).toEqual(moduleID);
expect(flushedQueue[METHOD_IDS][index]).toEqual(methodID);
expect(flushedQueue[PARAMS][index]).toEqual(params);
};
var queue;
describe('MessageQueue', () => {
beforeEach(() => {
queue = new MessageQueue(
remoteModulesConfig,
localModulesConfig,
customRequire,
);
TestModule.testHook1 = jasmine.createSpy();
TestModule.testHook2 = jasmine.createSpy();
});
it('should enqueue native calls', () => {
queue.__nativeCall(0, 1, [2]);
let flushedQueue = queue.flushedQueue();
assertQueue(flushedQueue, 0, 0, 1, [2]);
});
it('should call a local function with id', () => {
expect(TestModule.testHook1.callCount).toEqual(0);
queue.__callFunction(0, 0, [1]);
expect(TestModule.testHook1.callCount).toEqual(1);
});
it('should call a local function with the function name', () => {
expect(TestModule.testHook2.callCount).toEqual(0);
queue.__callFunction('one', 'testHook2', [2]);
expect(TestModule.testHook2.callCount).toEqual(1);
});
it('should generate native modules', () => {
queue.RemoteModules.one.remoteMethod1('foo');
let flushedQueue = queue.flushedQueue();
assertQueue(flushedQueue, 0, 0, 0, ['foo']);
});
it('should store callbacks', () => {
queue.RemoteModules.one.remoteMethod2('foo', () => {}, () => {});
let flushedQueue = queue.flushedQueue();
assertQueue(flushedQueue, 0, 0, 1, ['foo', 0, 1]);
});
it('should call the stored callback', (done) => {
var done = false;
queue.RemoteModules.one.remoteMethod1(() => { done = true; });
queue.__invokeCallback(1);
expect(done).toEqual(true);
});
it('should throw when calling the same callback twice', () => {
queue.RemoteModules.one.remoteMethod1(() => {});
queue.__invokeCallback(1);
expect(() => queue.__invokeCallback(1)).toThrow();
});
it('should throw when calling both success and failure callback', () => {
queue.RemoteModules.one.remoteMethod1(() => {}, () => {});
queue.__invokeCallback(1);
expect(() => queue.__invokeCallback(0)).toThrow();
});
describe('processBatch', () => {
it('should call __invokeCallback for invokeCallbackAndReturnFlushedQueue', () => {
queue.__invokeCallback = jasmine.createSpy();
queue.processBatch([{
method: 'invokeCallbackAndReturnFlushedQueue',
args: [],
}]);
expect(queue.__invokeCallback.callCount).toEqual(1);
});
it('should call __callFunction for callFunctionReturnFlushedQueue', () => {
queue.__callFunction = jasmine.createSpy();
queue.processBatch([{
method: 'callFunctionReturnFlushedQueue',
args: [],
}]);
expect(queue.__callFunction.callCount).toEqual(1);
});
});
});
var remoteModulesConfig = {
'one': {
'moduleID':0,
'methods': {
'remoteMethod1':{ 'type': 'remote', 'methodID': 0 },
'remoteMethod2':{ 'type': 'remote', 'methodID': 1 },
}
},
};
var localModulesConfig = {
'one': {
'moduleID': 0,
'methods': {
'testHook1':{ 'type': 'local', 'methodID': 0 },
'testHook2':{ 'type': 'local', 'methodID': 1 },
}
},
};

View File

@ -116,7 +116,7 @@ var inline = function(func, replaceWithArgs) {
return '\\b' + paramName + '\\b';
}).join('|');
var replaceRegex = new RegExp(replaceRegexStr, 'g');
var fnBody = fnStr.substring(fnStr.indexOf('{') + 1, fnStr.lastIndexOf('}') - 1);
var fnBody = fnStr.substring(fnStr.indexOf('{') + 1, fnStr.lastIndexOf('}'));
var newFnBody = fnBody.replace(replaceRegex, function(parameterName) {
var indexInParameterNames = parameterNames.indexOf(parameterName);
var replacementName = replaceWithArgs[indexInParameterNames];

View File

@ -47,6 +47,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
AppStateIOS: require('AppStateIOS'),
AsyncStorage: require('AsyncStorage'),
CameraRoll: require('CameraRoll'),
ImagePickerIOS: require('ImagePickerIOS'),
InteractionManager: require('InteractionManager'),
LayoutAnimation: require('LayoutAnimation'),
LinkingIOS: require('LinkingIOS'),

View File

@ -73,6 +73,11 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY;
* // Another component has become the responder, so this gesture
* // should be cancelled
* },
* onShouldBlockNativeResponder: (evt, gestureState) => {
* // Returns whether this component should block native components from becoming the JS
* // responder. Returns true by default. Is currently only supported on android.
* return true;
* },
* });
* },
*
@ -241,6 +246,7 @@ var PanResponder = {
* - `onPanResponderMove: (e, gestureState) => {...}`
* - `onPanResponderTerminate: (e, gestureState) => {...}`
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
* - 'onShouldBlockNativeResponder: (e, gestureState) => {...}'
*
* In general, for events that have capture equivalents, we update the
* gestureState once in the capture phase and can use it in the bubble phase
@ -298,6 +304,9 @@ var PanResponder = {
gestureState.dx = 0;
gestureState.dy = 0;
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
// TODO: t7467124 investigate if this can be removed
return config.onShouldBlockNativeResponder === undefined ? true :
config.onShouldBlockNativeResponder();
},
onResponderReject: function(e) {

View File

@ -55,13 +55,14 @@ var trackedTouchCount = 0;
*/
var previousActiveTouches = 0;
var changeResponder = function(nextResponderID) {
var changeResponder = function(nextResponderID, blockNativeResponder) {
var oldResponderID = responderID;
responderID = nextResponderID;
if (ResponderEventPlugin.GlobalResponderHandler !== null) {
ResponderEventPlugin.GlobalResponderHandler.onChange(
oldResponderID,
nextResponderID
nextResponderID,
blockNativeResponder
);
}
};
@ -379,6 +380,7 @@ function setResponderAndExtractTransfer(
grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(grantEvent);
var blockNativeResponder = executeDirectDispatch(grantEvent) === true;
if (responderID) {
var terminationRequestEvent = ResponderSyntheticEvent.getPooled(
@ -404,7 +406,7 @@ function setResponderAndExtractTransfer(
terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
changeResponder(wantsResponderID);
changeResponder(wantsResponderID, blockNativeResponder);
} else {
var rejectEvent = ResponderSyntheticEvent.getPooled(
eventTypes.responderReject,
@ -417,7 +419,7 @@ function setResponderAndExtractTransfer(
}
} else {
extracted = accumulate(extracted, grantEvent);
changeResponder(wantsResponderID);
changeResponder(wantsResponderID, blockNativeResponder);
}
return extracted;
}

View File

@ -20,6 +20,7 @@
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
#import "RCTPerformanceLogger.h"
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
@ -31,8 +32,6 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
dispatch_queue_t const RCTJSThread = nil;
/**
* Must be kept in sync with `MessageQueue.js`.
*/
@ -40,9 +39,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
RCTBridgeFieldResponseCBIDs,
RCTBridgeFieldResponseReturnValues,
RCTBridgeFieldFlushDateMillis
};
typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) {
@ -166,7 +162,6 @@ static NSDictionary *RCTJSErrorFromNSError(NSError *error)
SEL _selector;
NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks;
dispatch_block_t _methodQueue;
}
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
@ -570,14 +565,19 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
@implementation RCTBridge
static id<RCTJavaScriptExecutor> _latestJSExecutor;
#if RCT_DEBUG
dispatch_queue_t RCTJSThread;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Set up JS thread
RCTJSThread = (id)kCFNull;
#if RCT_DEBUG
// Set up module classes
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
@ -590,7 +590,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClassesByID containsObject:cls]) {
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
RCTLogError(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
}
break;
}
@ -600,11 +601,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
free(classes);
#endif
});
}
#endif
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
@ -612,6 +613,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
RCTAssertMainThread();
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
@ -744,7 +747,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
CADisplayLink *_mainDisplayLink;
CADisplayLink *_jsDisplayLink;
@ -786,7 +788,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
* Initialize and register bridge modules *before* adding the display link
* so we don't have threading issues
*/
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
[self registerModules];
/**
@ -899,21 +900,49 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
}
}
// Get method queues
[_modulesByID enumerateObjectsUsingBlock:
^(id<RCTBridgeModule> module, NSNumber *moduleID, __unused BOOL *stop) {
if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t queue = [module methodQueue];
if (queue) {
_queuesByID[moduleID] = queue;
} else {
_queuesByID[moduleID] = (id)kCFNull;
// Set/get method queues
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, __unused BOOL *stop) {
dispatch_queue_t queue = nil;
BOOL implementsMethodQueue = [module respondsToSelector:@selector(methodQueue)];
if (implementsMethodQueue) {
queue = [module methodQueue];
}
if (!queue) {
// Need to cache queueNames because they aren't retained by dispatch_queue
static NSMutableDictionary *queueNames;
if (!queueNames) {
queueNames = [[NSMutableDictionary alloc] init];
}
NSString *moduleName = RCTBridgeModuleNameForClass([module class]);
NSString *queueName = queueNames[moduleName];
if (!queueName) {
queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", moduleName];
queueNames[moduleName] = queueName;
}
// Create new queue
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
// assign it to the module
if (implementsMethodQueue) {
@try {
[(id)module setValue:queue forKey:@"methodQueue"];
}
@catch (NSException *exception) {
RCTLogError(@"%@ is returning nil for it's methodQueue, which is not "
"permitted. You must either return a pre-initialized "
"queue, or @synthesize the methodQueue to let the bridge "
"create a queue for you.", moduleName);
}
}
}
_queuesByID[moduleID] = queue;
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:module];
}
}
}];
}
@ -954,14 +983,20 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
});
} else {
RCTProfileBeginEvent();
RCTPerformanceLoggerStart(RCTPLScriptDownload);
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
RCTProfileEndEvent(@"JavaScript dowload", @"init,download", @[]);
_loading = NO;
if (!self.isValid) {
return;
}
[[RCTRedBox sharedInstance] dismiss];
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
@ -1009,11 +1044,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
- (NSDictionary *)modules
{
if (!self.isValid) {
return nil;
}
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
RCTAssert(!self.isValid || _modulesByName != nil, @"Bridge modules have not yet been initialized. "
"You may be trying to access a module too early in the startup procedure.");
return _modulesByName;
@ -1041,19 +1072,21 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
_mainDisplayLink = nil;
// Invalidate modules
dispatch_group_t group = dispatch_group_create();
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
[self dispatchBlock:^{
[(id<RCTInvalidating>)target invalidate];
} forModule:target];
} forModule:target dispatchGroup:group];
}
_queuesByID[RCTModuleIDsByName[RCTBridgeModuleNameForClass([target class])]] = nil;
}
// Release modules (breaks retain cycle if module has strong bridge reference)
_frameUpdateObservers = nil;
_modulesByID = nil;
_queuesByID = nil;
_modulesByName = nil;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
_queuesByID = nil;
_modulesByID = nil;
_modulesByName = nil;
_frameUpdateObservers = nil;
});
};
if (!_javaScriptExecutor) {
@ -1122,12 +1155,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginEvent();
RCTProfileBeginFlowEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTProfileEndFlowEvent();
RCTAssertJSThread();
RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError);
if (scriptLoadError) {
onComplete(scriptLoadError);
return;
@ -1153,13 +1185,30 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
}
#pragma mark - Payload Generation
- (void)dispatchBlock:(dispatch_block_t)block forModule:(id<RCTBridgeModule>)module
- (void)dispatchBlock:(dispatch_block_t)block
forModule:(id<RCTBridgeModule>)module
{
[self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]];
[self dispatchBlock:block forModule:module dispatchGroup:NULL];
}
- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID
- (void)dispatchBlock:(dispatch_block_t)block
forModule:(id<RCTBridgeModule>)module
dispatchGroup:(dispatch_group_t)group
{
[self dispatchBlock:block
forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]
dispatchGroup:group];
}
- (void)dispatchBlock:(dispatch_block_t)block
forModuleID:(NSNumber *)moduleID
{
[self dispatchBlock:block forModuleID:moduleID dispatchGroup:NULL];
}
- (void)dispatchBlock:(dispatch_block_t)block
forModuleID:(NSNumber *)moduleID
dispatchGroup:(dispatch_group_t)group
{
RCTAssertJSThread();
@ -1168,10 +1217,14 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
queue = _queuesByID[moduleID];
}
if (queue == (id)kCFNull) {
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else {
dispatch_async(queue ?: _methodQueue, block);
} else if (queue) {
if (group != NULL) {
dispatch_group_async(group, queue, block);
} else {
dispatch_async(queue, block);
}
}
}
@ -1258,14 +1311,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
return;
}
NSUInteger bufferRowCount = [requestsArray count];
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
if (bufferRowCount != expectedFieldsCount) {
RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
return;
}
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
id field = [requestsArray objectAtIndex:fieldIndex];
if (![field isKindOfClass:[NSArray class]]) {
@ -1319,9 +1364,9 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
};
if (queue == (id)kCFNull) {
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else {
} else if (queue) {
dispatch_async(queue, block);
}
}
@ -1484,6 +1529,14 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
^(__unused NSData *data, __unused NSURLResponse *response, NSError *error) {
if (error) {
RCTLogError(@"%@", error.localizedDescription);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:@"Profile"
message:@"The profile has been generated, check the dev server log for instructions."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
});
}
}];

View File

@ -41,7 +41,7 @@ typedef void (^RCTPromiseRejectBlock)(NSError *error);
*
* NOTE: RCTJSThread is not a real libdispatch queue
*/
extern const dispatch_queue_t RCTJSThread;
extern dispatch_queue_t RCTJSThread;
/**
* Provides the interface needed to register a bridge module.
@ -53,10 +53,33 @@ extern const dispatch_queue_t RCTJSThread;
* A reference to the RCTBridge. Useful for modules that require access
* to bridge features, such as sending events or making JS calls. This
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add @synthesize bridge = _bridge;
* To implement this in your module, just add `@synthesize bridge = _bridge;`
*/
@property (nonatomic, weak) RCTBridge *bridge;
/**
* The queue that will be used to call all exported methods. If omitted, this
* will call on a default background queue, which is avoids blocking the main
* thread.
*
* If the methods in your module need to interact with UIKit methods, they will
* probably need to call those on the main thread, as most of UIKit is main-
* thread-only. You can tell React Native to call your module methods on the
* main thread by returning a reference to the main queue, like this:
*
* - (dispatch_queue_t)methodQueue
* {
* return dispatch_get_main_queue();
* }
*
* If you don't want to specify the queue yourself, but you need to use it
* inside your class (e.g. if you have internal methods that need to disaptch
* onto that queue), you can just add `@synthesize methodQueue = _methodQueue;`
* and the bridge will populate the methodQueue property for you automatically
* when it initializes the module.
*/
@property (nonatomic, weak, readonly) dispatch_queue_t methodQueue;
/**
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_name argument
@ -180,38 +203,6 @@ extern const dispatch_queue_t RCTJSThread;
return @[@#js_name, @#method]; \
} \
/**
* The queue that will be used to call all exported methods. If omitted, this
* will call on the default background queue, which is avoids blocking the main
* thread.
*
* If the methods in your module need to interact with UIKit methods, they will
* probably need to call those on the main thread, as most of UIKit is main-
* thread-only. You can tell React Native to call your module methods on the
* main thread by returning a reference to the main queue, like this:
*
* - (dispatch_queue_t)methodQueue
* {
* return dispatch_get_main_queue();
* }
*
* If your methods perform heavy work such as synchronous filesystem or network
* access, you probably don't want to block the default background queue, as
* this will stall other methods. Instead, you should return a custom serial
* queue, like this:
*
* - (dispatch_queue_t)methodQueue
* {
* return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL);
* }
*
* Alternatively, if only some methods of the module should be executed on a
* particular queue you can leave this method unimplemented, and simply
* dispatch_async() to the required queue within the method itself.
*/
- (dispatch_queue_t)methodQueue;
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.ModuleName.X. This method is called when the module is

View File

@ -46,6 +46,9 @@
+ (NSURL *)NSURL:(id)json;
+ (NSURLRequest *)NSURLRequest:(id)json;
typedef NSURL RCTFileURL;
+ (RCTFileURL *)RCTFileURL:(id)json;
+ (NSDate *)NSDate:(id)json;
+ (NSTimeZone *)NSTimeZone:(id)json;
+ (NSTimeInterval)NSTimeInterval:(id)json;
@ -95,6 +98,9 @@ typedef NSArray NSDictionaryArray;
typedef NSArray NSURLArray;
+ (NSURLArray *)NSURLArray:(id)json;
typedef NSArray RCTFileURLArray;
+ (RCTFileURLArray *)RCTFileURLArray:(id)json;
typedef NSArray NSNumberArray;
+ (NSNumberArray *)NSNumberArray:(id)json;

View File

@ -122,6 +122,20 @@ RCT_CONVERTER(NSString *, NSString, description)
return URL ? [NSURLRequest requestWithURL:URL] : nil;
}
+ (RCTFileURL *)RCTFileURL:(id)json
{
NSURL *fileURL = [self NSURL:json];
if (![fileURL isFileURL]) {
RCTLogError(@"URI must be a local file, '%@' isn't.", fileURL);
return nil;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
RCTLogError(@"File '%@' could not be found.", fileURL);
return nil;
}
return fileURL;
}
+ (NSDate *)NSDate:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
@ -854,6 +868,7 @@ NSArray *RCTConvertArrayValue(SEL type, id json)
RCT_ARRAY_CONVERTER(NSString)
RCT_ARRAY_CONVERTER(NSDictionary)
RCT_ARRAY_CONVERTER(NSURL)
RCT_ARRAY_CONVERTER(RCTFileURL)
RCT_ARRAY_CONVERTER(NSNumber)
RCT_ARRAY_CONVERTER(UIColor)

View File

@ -119,6 +119,11 @@ RCT_EXPORT_MODULE()
name:RCTJavaScriptDidLoadNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(jsLoaded:)
name:RCTJavaScriptDidFailToLoadNotification
object:nil];
_defaults = [NSUserDefaults standardUserDefaults];
_settings = [[NSMutableDictionary alloc] init];
_extraMenuItems = [NSMutableArray array];
@ -142,7 +147,7 @@ RCT_EXPORT_MODULE()
[commands registerKeyCommandWithInput:@"i"
modifierFlags:UIKeyModifierCommand
action:^(__unused UIKeyCommand *command) {
[_bridge.eventDispatcher
[weakSelf.bridge.eventDispatcher
sendDeviceEventWithName:@"toggleElementInspector"
body:nil];
}];

View File

@ -11,28 +11,112 @@
#import <UIKit/UIKit.h>
#import "RCTDefines.h"
#import "RCTUtils.h"
#if RCT_DEV
static BOOL RCTIsIOS8OrEarlier()
{
return [UIDevice currentDevice].systemVersion.floatValue < 9;
}
@interface RCTKeyCommand : NSObject <NSCopying>
@property (nonatomic, strong) UIKeyCommand *keyCommand;
@property (nonatomic, copy) void (^block)(UIKeyCommand *);
@end
@implementation RCTKeyCommand
- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand
block:(void (^)(UIKeyCommand *))block
{
if ((self = [super init])) {
_keyCommand = keyCommand;
_block = block;
}
return self;
}
RCT_NOT_IMPLEMENTED(-init)
- (id)copyWithZone:(__unused NSZone *)zone
{
return self;
}
- (NSUInteger)hash
{
return _keyCommand.input.hash ^ _keyCommand.modifierFlags;
}
- (BOOL)isEqual:(RCTKeyCommand *)object
{
if (![object isKindOfClass:[RCTKeyCommand class]]) {
return NO;
}
return [self matchesInput:object.keyCommand.input
flags:object.keyCommand.modifierFlags];
}
- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
{
return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>",
[self class], self, _keyCommand.input, _keyCommand.modifierFlags,
_block ? @"YES" : @"NO"];
}
@end
@interface RCTKeyCommands ()
@property (nonatomic, strong) NSMutableDictionary *commandBindings;
@property (nonatomic, strong) NSMutableSet *commands;
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key;
@end
@implementation UIResponder (RCTKeyCommands)
- (NSArray *)RCT_keyCommands
{
NSSet *commands = [RCTKeyCommands sharedInstance].commands;
return [[commands valueForKeyPath:@"keyCommand"] allObjects];
}
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key
{
// NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
// method gets called repeatedly if the command key is held down.
static NSTimeInterval lastCommand = 0;
if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastCommand > 0.5) {
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
if ([command.keyCommand.input isEqualToString:key.input] &&
command.keyCommand.modifierFlags == key.modifierFlags) {
if (command.block) {
command.block(key);
lastCommand = CACurrentMediaTime();
}
}
}
}
}
@end
@implementation UIApplication (RCTKeyCommands)
- (NSArray *)RCT_keyCommands
{
NSDictionary *commandBindings = [RCTKeyCommands sharedInstance].commandBindings;
return [[self RCT_keyCommands] arrayByAddingObjectsFromArray:[commandBindings allKeys]];
}
// Required for iOS 8.x
- (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
{
if (action == @selector(RCT_handleKeyCommand:)) {
[[RCTKeyCommands sharedInstance] RCT_handleKeyCommand:sender];
[self RCT_handleKeyCommand:sender];
return YES;
}
return [self RCT_sendAction:action to:target from:sender forEvent:event];
@ -44,12 +128,24 @@
+ (void)initialize
{
//swizzle UIApplication
RCTSwapInstanceMethods([UIApplication class], @selector(keyCommands), @selector(RCT_keyCommands));
RCTSwapInstanceMethods([UIApplication class], @selector(sendAction:to:from:forEvent:), @selector(RCT_sendAction:to:from:forEvent:));
}
if (RCTIsIOS8OrEarlier()) {
static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
//swizzle UIApplication
RCTSwapInstanceMethods([UIApplication class],
@selector(keyCommands),
@selector(RCT_keyCommands));
RCTSwapInstanceMethods([UIApplication class],
@selector(sendAction:to:from:forEvent:),
@selector(RCT_sendAction:to:from:forEvent:));
} else {
//swizzle UIResponder
RCTSwapInstanceMethods([UIResponder class],
@selector(keyCommands),
@selector(RCT_keyCommands));
}
}
+ (instancetype)sharedInstance
{
@ -65,36 +161,22 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
- (instancetype)init
{
if ((self = [super init])) {
_commandBindings = [[NSMutableDictionary alloc] init];
_commands = [[NSMutableSet alloc] init];
}
return self;
}
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key
{
// NOTE: We should just be able to do commandBindings[key] here, but curiously, the
// lookup seems to return nil sometimes, even if the key is found in the dictionary.
// To fix this, we use a linear search, since there won't be many keys anyway
[_commandBindings enumerateKeysAndObjectsUsingBlock:
^(UIKeyCommand *k, void (^block)(UIKeyCommand *), __unused BOOL *stop) {
if ([key.input isEqualToString:k.input] && key.modifierFlags == k.modifierFlags) {
block(key);
}
}];
}
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block
{
RCTAssertMainThread();
if (input.length && flags) {
if (input.length && flags && RCTIsIOS8OrEarlier()) {
// Workaround around the first cmd not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This ensures that
// command-key modified commands will work first time.
// command-key modified commands will work first time. Fixed in iOS 9.
[self registerKeyCommandWithInput:@""
modifierFlags:flags
@ -105,7 +187,9 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
modifierFlags:flags
action:@selector(RCT_handleKeyCommand:)];
_commandBindings[command] = block ?: ^(__unused UIKeyCommand *cmd) {};
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
[_commands removeObject:keyCommand];
[_commands addObject:keyCommand];
}
- (void)unregisterKeyCommandWithInput:(NSString *)input
@ -113,9 +197,9 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
{
RCTAssertMainThread();
for (UIKeyCommand *key in [_commandBindings allKeys]) {
if ([key.input isEqualToString:input] && key.modifierFlags == flags) {
[_commandBindings removeObjectForKey:key];
for (RCTKeyCommand *command in _commands.allObjects) {
if ([command matchesInput:input flags:flags]) {
[_commands removeObject:command];
break;
}
}
@ -126,8 +210,8 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
{
RCTAssertMainThread();
for (UIKeyCommand *key in [_commandBindings allKeys]) {
if ([key.input isEqualToString:input] && key.modifierFlags == flags) {
for (RCTKeyCommand *command in _commands) {
if ([command matchesInput:input flags:flags]) {
return YES;
}
}
@ -135,3 +219,29 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
}
@end
#else
@implementation RCTKeyCommands
+ (instancetype)sharedInstance
{
return nil;
}
- (void)registerKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
action:(void (^)(UIKeyCommand *))block {}
- (void)unregisterKeyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags {}
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)flags
{
return NO;
}
@end
#endif

View File

@ -41,7 +41,7 @@ RCT_EXPORT_MODULE()
- (RCTFPSGraph *)jsGraph
{
if (!_jsGraph) {
if (!_jsGraph && _container) {
UIColor *jsColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:1];
_jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34)
graphPosition:RCTFPSGraphPositionRight
@ -54,7 +54,7 @@ RCT_EXPORT_MODULE()
- (RCTFPSGraph *)uiGraph
{
if (!_uiGraph) {
if (!_uiGraph && _container) {
UIColor *uiColor = [UIColor colorWithRed:0 green:1 blue:1 alpha:1];
_uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34)
graphPosition:RCTFPSGraphPositionLeft

View File

@ -0,0 +1,24 @@
/**
* 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 <Foundation/Foundation.h>
#import "RCTDefines.h"
#import "RCTBridgeModule.h"
typedef NS_ENUM(NSUInteger, RCTPLTag) {
RCTPLScriptDownload = 0,
RCTPLAppScriptExecution,
RCTPLTTI,
RCTPLSize
};
void RCTPerformanceLoggerStart(RCTPLTag tag);
void RCTPerformanceLoggerEnd(RCTPLTag tag);
NSArray *RCTPerformanceLoggerOutput(void);

View File

@ -0,0 +1,77 @@
/**
* 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 <QuartzCore/QuartzCore.h>
#import "RCTPerformanceLogger.h"
#import "RCTRootView.h"
static int64_t RCTPLData[RCTPLSize][2] = {};
void RCTPerformanceLoggerStart(RCTPLTag tag)
{
RCTPLData[tag][0] = CACurrentMediaTime() * 1000;
}
void RCTPerformanceLoggerEnd(RCTPLTag tag)
{
RCTPLData[tag][1] = CACurrentMediaTime() * 1000;
}
NSArray *RCTPerformanceLoggerOutput(void)
{
return @[
@(RCTPLData[0][0]),
@(RCTPLData[0][1]),
@(RCTPLData[1][0]),
@(RCTPLData[1][1]),
@(RCTPLData[2][0]),
@(RCTPLData[2][1]),
];
}
@interface RCTPerformanceLogger : NSObject <RCTBridgeModule>
@end
@implementation RCTPerformanceLogger
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (instancetype)init
{
if ((self = [super init])) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(sendTimespans)
name:RCTContentDidAppearNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)sendTimespans
{
[_bridge enqueueJSCall:@"PerformanceLogger.addTimespans" args:@[
RCTPerformanceLoggerOutput(),
@[
@"ScriptDownload",
@"ScriptExecution",
@"TTI",
],
]];
}
@end

View File

@ -20,8 +20,8 @@
* before before using it.
*/
NSString *const RCTProfileDidStartProfiling;
NSString *const RCTProfileDidEndProfiling;
RCT_EXTERN NSString *const RCTProfileDidStartProfiling;
RCT_EXTERN NSString *const RCTProfileDidEndProfiling;
#if RCT_DEV

View File

@ -172,6 +172,8 @@ static void RCTProfileHookModules(RCTBridge *bridge)
}
free(methods);
class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:");
for (Class cls in @[proxyClass, object_getClass(proxyClass)]) {
Method oldImp = class_getInstanceMethod(cls, @selector(class));
class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp));

View File

@ -17,6 +17,7 @@
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerformanceLogger.h"
#import "RCTSourceCode.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
@ -247,6 +248,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
RCTPerformanceLoggerEnd(RCTPLTTI);
dispatch_async(dispatch_get_main_queue(), ^{
if (!_contentHasAppeared) {
_contentHasAppeared = YES;

View File

@ -17,6 +17,7 @@
#import "RCTDefines.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#import "RCTPerformanceLogger.h"
#import "RCTUtils.h"
@interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@ -446,12 +447,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
if (!strongSelf || !strongSelf.isValid) {
return;
}
RCTPerformanceLoggerStart(RCTPLAppScriptExecution);
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError);
JSStringRelease(jsURL);
JSStringRelease(execJSString);
RCTPerformanceLoggerEnd(RCTPLAppScriptExecution);
if (onComplete) {
NSError *error;

View File

@ -8,6 +8,7 @@
*/
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
/**
* A simple, asynchronous, persistent, key-value storage system designed as a
@ -20,7 +21,9 @@
*
* Keys and values must always be strings or an error is returned.
*/
@interface RCTAsyncLocalStorage : NSObject <RCTBridgeModule>
@interface RCTAsyncLocalStorage : NSObject <RCTBridgeModule,RCTInvalidating>
@property (nonatomic, assign) BOOL clearOnInvalidate;
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback;
@ -28,4 +31,7 @@
- (void)clear:(RCTResponseSenderBlock)callback;
- (void)getAllKeys:(RCTResponseSenderBlock)callback;
// For clearing data when the bridge may not exist, e.g. when logging out.
+ (void)clearAllData;
@end

Some files were not shown because too many files have changed in this diff Show More