Updates from Wed 24 Jun
This commit is contained in:
commit
2e4cbc41b0
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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.';
|
||||
|
|
|
@ -236,6 +236,7 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
exports.title = '<MapView>';
|
||||
exports.description = 'Base component to display maps';
|
||||
exports.examples = [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -60,6 +60,7 @@ var ProgressViewExample = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
exports.framework = 'React';
|
||||
exports.title = 'ProgressViewIOS';
|
||||
exports.description = 'ProgressViewIOS';
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -26,6 +26,7 @@ var {
|
|||
View,
|
||||
} = React;
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
exports.title = '<Touchable*> and onPress';
|
||||
exports.examples = [
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -53,6 +53,8 @@ var AppEventsTest = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
AppEventsTest.displayName = 'AppEventsTest';
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
margin: 40,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -59,4 +59,6 @@ var IntegrationTestHarnessTest = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
IntegrationTestHarnessTest.displayName = 'IntegrationTestHarnessTest';
|
||||
|
||||
module.exports = IntegrationTestHarnessTest;
|
||||
|
|
|
@ -164,4 +164,6 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
LayoutEventsTest.displayName = 'LayoutEventsTest';
|
||||
|
||||
module.exports = LayoutEventsTest;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -152,4 +152,6 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
TimersTest.displayName = 'TimersTest';
|
||||
|
||||
module.exports = TimersTest;
|
||||
|
|
|
@ -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
|
||||
)
|
||||
));
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
|
@ -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 = [
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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')
|
||||
);
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -88,9 +88,11 @@ var styles = StyleSheet.create({
|
|||
justifyContent: 'center',
|
||||
},
|
||||
sizeSmall: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
sizeLarge: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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}
|
||||
/>;
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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]);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -178,7 +178,6 @@ var nativeOnlyProps = {
|
|||
src: true,
|
||||
defaultImageSrc: true,
|
||||
imageTag: true,
|
||||
resizeMode: true,
|
||||
};
|
||||
if (__DEV__) {
|
||||
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
|
||||
|
|
|
@ -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;
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
// Just to make sure the JS gets packaged up.
|
||||
require('RCTDeviceEventEmitter');
|
||||
require('PerformanceLogger');
|
||||
|
||||
if (typeof GLOBAL === 'undefined') {
|
||||
GLOBAL = this;
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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([]);
|
||||
});
|
||||
|
||||
});
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTDataManager : NSObject <RCTBridgeModule>
|
||||
@interface RCTNetworking : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
|
|
@ -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
|
||||
{
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ class XMLHttpRequestBase {
|
|||
return;
|
||||
}
|
||||
this.status = status;
|
||||
this.setResponseHeaders(responseHeaders);
|
||||
this.setResponseHeaders(responseHeaders || {});
|
||||
this.responseText = responseText;
|
||||
this.setReadyState(this.DONE);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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']
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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 },
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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];
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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];
|
||||
}];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue