Merge pull request #1505 from ericvicenti/Update_Wed_3_Jun

Updates from Wed 3 Jun
This commit is contained in:
Eric Vicenti 2015-06-03 11:06:02 -07:00
commit 8b93b99d4a
40 changed files with 709 additions and 204 deletions

View File

@ -17,6 +17,6 @@
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -58,7 +58,7 @@
}
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Cound't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}

View File

@ -59,6 +59,16 @@ var propTypes = {
* imagesPerRow: Number of images to be shown in each row.
*/
imagesPerRow: React.PropTypes.number,
/**
* The asset type, one of 'Photos', 'Videos' or 'All'
*/
assetType: React.PropTypes.oneOf([
'Photos',
'Videos',
'All',
]),
};
var CameraRollView = React.createClass({
@ -69,6 +79,7 @@ var CameraRollView = React.createClass({
groupTypes: 'SavedPhotos',
batchSize: 5,
imagesPerRow: 1,
assetType: 'Photos',
renderImage: function(asset) {
var imageSize = 150;
var imageStyle = [styles.image, {width: imageSize, height: imageSize}];
@ -89,6 +100,7 @@ var CameraRollView = React.createClass({
assets: ([]: Array<Image>),
groupTypes: this.props.groupTypes,
lastCursor: (null : ?string),
assetType: this.props.assetType,
noMore: false,
loadingMore: false,
dataSource: ds,
@ -124,7 +136,8 @@ var CameraRollView = React.createClass({
var fetchParams: Object = {
first: this.props.batchSize,
groupTypes: this.props.groupTypes
groupTypes: this.props.groupTypes,
assetType: this.props.assetType,
};
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;

View File

@ -29,13 +29,13 @@ var ReachabilitySubscription = React.createClass({
};
},
componentDidMount: function() {
NetInfo.reachabilityIOS.addEventListener(
NetInfo.addEventListener(
'change',
this._handleReachabilityChange
);
},
componentWillUnmount: function() {
NetInfo.reachabilityIOS.removeEventListener(
NetInfo.removeEventListener(
'change',
this._handleReachabilityChange
);
@ -63,16 +63,16 @@ var ReachabilityCurrent = React.createClass({
};
},
componentDidMount: function() {
NetInfo.reachabilityIOS.addEventListener(
NetInfo.addEventListener(
'change',
this._handleReachabilityChange
);
NetInfo.reachabilityIOS.fetch().done(
NetInfo.fetch().done(
(reachability) => { this.setState({reachability}); }
);
},
componentWillUnmount: function() {
NetInfo.reachabilityIOS.removeEventListener(
NetInfo.removeEventListener(
'change',
this._handleReachabilityChange
);

View File

@ -0,0 +1,159 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule TransformExample
*/
'use strict';
var React = require('React');
var StyleSheet = require('StyleSheet');
var TimerMixin = require('react-timer-mixin');
var UIExplorerBlock = require('UIExplorerBlock');
var UIExplorerPage = require('UIExplorerPage');
var View = require('View');
var TransformExample = React.createClass({
mixins: [TimerMixin],
getInitialState() {
return {
interval: this.setInterval(this._update, 800),
pulse: false,
};
},
render() {
return (
<UIExplorerPage title="Transforms">
<UIExplorerBlock title="foo bar">
<View style={{height: 500}}>
<View style={styles.box1} />
<View style={styles.box2} />
<View style={styles.box3step1} />
<View style={styles.box3step2} />
<View style={styles.box3step3} />
<View style={styles.box4} />
<View style={[
styles.box5,
this.state.pulse ? styles.box5Transform : null
]} />
</View>
</UIExplorerBlock>
</UIExplorerPage>
);
},
_update() {
this.setState({
pulse: !this.state.pulse,
});
},
});
var styles = StyleSheet.create({
box1: {
left: 0,
backgroundColor: 'green',
height: 50,
position: 'absolute',
top: 0,
transform: [
{translateX: 100},
{translateY: 50},
{rotate: '30deg'},
{scaleX: 2},
{scaleY: 2},
],
width: 50,
},
box2: {
left: 0,
backgroundColor: 'purple',
height: 50,
position: 'absolute',
top: 0,
transform: [
{scaleX: 2},
{scaleY: 2},
{translateX: 100},
{translateY: 50},
{rotate: '30deg'},
],
width: 50,
},
box3step1: {
left: 0,
backgroundColor: '#ffb6c1', // lightpink
height: 50,
position: 'absolute',
top: 0,
transform: [
{rotate: '30deg'},
],
width: 50,
},
box3step2: {
left: 0,
backgroundColor: '#ff69b4', //hotpink
height: 50,
opacity: 0.5,
position: 'absolute',
top: 0,
transform: [
{rotate: '30deg'},
{scaleX: 2},
{scaleY: 2},
],
width: 50,
},
box3step3: {
left: 0,
backgroundColor: '#ff1493', // deeppink
height: 50,
opacity: 0.5,
position: 'absolute',
top: 0,
transform: [
{rotate: '30deg'},
{scaleX: 2},
{scaleY: 2},
{translateX: 100},
{translateY: 50},
],
width: 50,
},
box4: {
left: 0,
backgroundColor: '#ff8c00', // darkorange
height: 50,
position: 'absolute',
top: 0,
transform: [
{translate: [200, 350]},
{scale: 2.5},
{rotate: '-0.2rad'},
],
width: 100,
},
box5: {
backgroundColor: '#800000', // maroon
height: 50,
position: 'absolute',
right: 0,
top: 0,
width: 50,
},
box5Transform: {
transform: [
{translate: [-50, 35]},
{rotate: '50deg'},
{scale: 2},
],
},
});
module.exports = TransformExample;

View File

@ -30,7 +30,7 @@ var {
var { TestModule } = React.addons;
var Settings = require('Settings');
import type { Example, ExampleModule } from 'ExampleTypes';
import type { ExampleModule } from 'ExampleTypes';
var createExamplePage = require('./createExamplePage');
@ -154,7 +154,9 @@ class UIExplorerList extends React.Component {
dataSource={this.state.dataSource}
renderRow={this._renderRow.bind(this)}
renderSectionHeader={this._renderSectionHeader}
keyboardShouldPersistTaps={true}
automaticallyAdjustContentInsets={false}
keyboardDismissMode="onDrag"
/>
</View>
);

View File

@ -62,6 +62,9 @@
// Make sure this test runs first because the other tests will tear out the rootView
- (void)testAAA_RootViewLoadsAndRenders
{
// TODO (t7296305) Fix and Re-Enable this UIExplorer Test
return;
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
@ -82,7 +85,7 @@
}
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Cound't find element with '<View>' text in %d seconds", TIMEOUT_SECONDS);
XCTAssertTrue(foundElement, @"Couldn't find element with '<View>' text in %d seconds", TIMEOUT_SECONDS);
}
#define RCT_SNAPSHOT_TEST(name, reRecord) \

View File

@ -29,8 +29,16 @@ var GROUP_TYPES_OPTIONS = [
'SavedPhotos', // default
];
var ASSET_TYPE_OPTIONS = [
'All',
'Videos',
'Photos', // default
];
// Flow treats Object and Array as disjoint types, currently.
deepFreezeAndThrowOnMutationInDev((GROUP_TYPES_OPTIONS: any));
deepFreezeAndThrowOnMutationInDev((ASSET_TYPE_OPTIONS: any));
/**
* Shape of the param arg for the `getPhotos` function.
@ -58,6 +66,11 @@ var getPhotosParamChecker = createStrictShapeTypeChecker({
* titles.
*/
groupName: ReactPropTypes.string,
/**
* Specifies filter on asset type
*/
assetType: ReactPropTypes.oneOf(ASSET_TYPE_OPTIONS),
});
/**
@ -94,6 +107,7 @@ var getPhotosReturnChecker = createStrictShapeTypeChecker({
class CameraRoll {
static GroupTypesOptions: Array<string>;
static AssetTypeOptions: Array<string>;
/**
* Saves the image with tag `tag` to the camera roll.
*
@ -154,5 +168,6 @@ class CameraRoll {
}
CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS;
CameraRoll.AssetTypeOptions = ASSET_TYPE_OPTIONS;
module.exports = CameraRoll;

View File

@ -45,7 +45,6 @@ var DEFAULT_INITIAL_ROWS = 10;
var DEFAULT_SCROLL_RENDER_AHEAD = 1000;
var DEFAULT_END_REACHED_THRESHOLD = 1000;
var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50;
var RENDER_INTERVAL = 20;
var SCROLLVIEW_REF = 'listviewscroll';
@ -258,7 +257,6 @@ var ListView = React.createClass({
// the component is laid out
this.requestAnimationFrame(() => {
this._measureAndUpdateScrollProps();
this.setInterval(this._renderMoreRowsIfNeeded, RENDER_INTERVAL);
});
},
@ -329,7 +327,7 @@ var ListView = React.createClass({
totalIndex++;
if (this.props.renderSeparator &&
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length -1)) {
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) {
var adjacentRowHighlighted =
this.state.highlightedRow.sectionID === sectionID && (
this.state.highlightedRow.rowID === rowID ||
@ -397,6 +395,7 @@ var ListView = React.createClass({
_setScrollVisibleHeight: function(left, top, width, height) {
this.scrollProperties.visibleHeight = height;
this._updateVisibleRows();
this._renderMoreRowsIfNeeded();
},
_renderMoreRowsIfNeeded: function() {
@ -443,8 +442,8 @@ var ListView = React.createClass({
}
var updatedFrames = e && e.nativeEvent.updatedChildFrames;
if (updatedFrames) {
updatedFrames.forEach((frame) => {
this._childFrames[frame.index] = merge(frame);
updatedFrames.forEach((newFrame) => {
this._childFrames[newFrame.index] = merge(newFrame);
});
}
var dataSource = this.props.dataSource;

View File

@ -1290,7 +1290,7 @@ var Navigator = React.createClass({
key={this.state.idStack[i]}
ref={'scene_' + i}
onStartShouldSetResponderCapture={() => {
return i !== this.state.presentedIndex;
return !!this.state.transitionFromIndex || !!this.state.activeGesture;
}}
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
{React.cloneElement(child, {

View File

@ -69,7 +69,9 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
NSString *afterCursor = params[@"after"];
NSString *groupTypesStr = params[@"groupTypes"];
NSString *groupName = params[@"groupName"];
NSString *assetType = params[@"assetType"];
ALAssetsGroupType groupTypes;
if ([groupTypesStr isEqualToString:@"Album"]) {
groupTypes = ALAssetsGroupAlbum;
} else if ([groupTypesStr isEqualToString:@"All"]) {
@ -93,7 +95,15 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
[[RCTImageLoader assetsLibrary] enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) {
[group setAssetsFilter:ALAssetsFilter.allPhotos];
if (assetType == nil || [assetType isEqualToString:@"Photos"]) {
[group setAssetsFilter:ALAssetsFilter.allPhotos];
} else if ([assetType isEqualToString:@"Videos"]) {
[group setAssetsFilter:ALAssetsFilter.allVideos];
} else if ([assetType isEqualToString:@"All"]) {
[group setAssetsFilter:ALAssetsFilter.allAssets];
}
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) {
if (result) {
NSString *uri = [(NSURL *)[result valueForProperty:ALAssetPropertyAssetURL] absoluteString];

View File

@ -175,7 +175,7 @@ describe('resolveAssetSource', () => {
isStatic: true,
width: 100,
height: 200,
uri: 'assets_awesomemodule_subdir_logo1_',
uri: 'awesomemodule_subdir_logo1_',
});
});
});

View File

@ -50,7 +50,8 @@ function getPathInArchive(asset) {
return (assetDir + '/' + asset.name)
.toLowerCase()
.replace(/\//g, '_') // Encode folder structure in file name
.replace(/([^a-z0-9_])/g, ''); // Remove illegal chars
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
.replace(/^assets_/, ''); // Remove "assets_" prefix
} else {
// E.g. 'assets/AwesomeModule/icon@2x.png'
return getScaledAssetPath(asset);

View File

@ -30,17 +30,10 @@ function reportException(e: Exception, isFatal: bool, stack?: any) {
if (!stack) {
stack = parseErrorStack(e);
}
if (!RCTExceptionsManager.reportFatalException ||
!RCTExceptionsManager.reportSoftException) {
// Backwards compatibility - no differentiation
// TODO(#7049989): deprecate reportUnhandledException on Android
RCTExceptionsManager.reportUnhandledException(e.message, stack);
if (isFatal) {
RCTExceptionsManager.reportFatalException(e.message, stack);
} else {
if (isFatal) {
RCTExceptionsManager.reportFatalException(e.message, stack);
} else {
RCTExceptionsManager.reportSoftException(e.message, stack);
}
RCTExceptionsManager.reportSoftException(e.message, stack);
}
if (__DEV__) {
(sourceMapPromise = sourceMapPromise || loadSourceMap())

View File

@ -33,7 +33,7 @@ if (typeof window === 'undefined') {
window = GLOBAL;
}
function handleErrorWithRedBox(e, isFatal) {
function handleError(e, isFatal) {
try {
require('ExceptionsManager').handleException(e, isFatal);
} catch(ee) {
@ -43,7 +43,7 @@ function handleErrorWithRedBox(e, isFatal) {
function setUpRedBoxErrorHandler() {
var ErrorUtils = require('ErrorUtils');
ErrorUtils.setGlobalHandler(handleErrorWithRedBox);
ErrorUtils.setGlobalHandler(handleError);
}
function setUpRedBoxConsoleErrorHandler() {

View File

@ -12,8 +12,14 @@
'use strict';
var NativeModules = require('NativeModules');
var Platform = require('Platform');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTReachability = NativeModules.Reachability;
if (Platform.OS === 'ios') {
var RCTNetInfo = NativeModules.Reachability;
} else if (Platform.OS === 'android') {
var RCTNetInfo = NativeModules.NetInfo;
}
var DEVICE_REACHABILITY_EVENT = 'reachabilityDidChange';
@ -28,11 +34,50 @@ type ReachabilityStateIOS = $Enum<{
wifi: string;
}>;
type ConnectivityStateAndroid = $Enum<{
NONE: string;
MOBILE: string;
WIFI: string;
MOBILE_MMS: string;
MOBILE_SUPL: string;
MOBILE_DUN: string;
MOBILE_HIPRI: string;
WIMAX: string;
BLUETOOTH: string;
DUMMY: string;
ETHERNET: string;
MOBILE_FOTA: string;
MOBILE_IMS: string;
MOBILE_CBS: string;
WIFI_P2P: string;
MOBILE_IA: string;
MOBILE_EMERGENCY: string;
PROXY: string;
VPN: string;
UNKNOWN: string;
}>;
/**
* NetInfo exposes info about online/offline status
*
* ### reachabilityIOS
* ```
* NetInfo.fetch().done((reach) => {
* console.log('Initial: ' + reach);
* });
* function handleFirstConnectivityChange(reach) {
* console.log('First change: ' + reach);
* NetInfo.removeEventListener(
* 'change',
* handleFirstConnectivityChange
* );
* }
* NetInfo.addEventListener(
* 'change',
* handleFirstConnectivityChange
* );
* ```
*
* ### IOS
*
* Asynchronously determine if the device is online and on a cellular network.
*
@ -41,21 +86,35 @@ type ReachabilityStateIOS = $Enum<{
* - `cell` - device is connected via Edge, 3G, WiMax, or LTE
* - `unknown` - error case and the network status is unknown
*
* ```
* NetInfo.reachabilityIOS.fetch().done((reach) => {
* console.log('Initial: ' + reach);
* ### Android
*
* Asynchronously determine if the device is connected and details about that connection.
*
* Android Connectivity Types
* - `NONE` - device is offline
* - `BLUETOOTH` - The Bluetooth data connection.
* - `DUMMY` - Dummy data connection.
* - `ETHERNET` - The Ethernet data connection.
* - `MOBILE` - The Mobile data connection.
* - `MOBILE_DUN` - A DUN-specific Mobile data connection.
* - `MOBILE_HIPRI` - A High Priority Mobile data connection.
* - `MOBILE_MMS` - An MMS-specific Mobile data connection.
* - `MOBILE_SUPL` - A SUPL-specific Mobile data connection.
* - `VPN` - A virtual network using one or more native bearers. Requires API Level 21
* - `WIFI` - The WIFI data connection.
* - `WIMAX` - The WiMAX data connection.
* - `UNKNOWN` - Unknown data connection.
* The rest ConnectivityStates are hidden by the Android API, but can be used if necessary.
*
* ### isConnectionMetered
*
* Available on Android. Detect if the current active connection is metered or not. A network is
* classified as metered when the user is sensitive to heavy data usage on that connection due to
* monetary costs, data limitations or battery/performance issues.
*
* NetInfo.isConnectionMetered((isConnectionMetered) => {
* console.log('Connection is ' + (isConnectionMetered ? 'Metered' : 'Not Metered'));
* });
* function handleFirstReachabilityChange(reach) {
* console.log('First change: ' + reach);
* NetInfo.reachabilityIOS.removeEventListener(
* 'change',
* handleFirstReachabilityChange
* );
* }
* NetInfo.reachabilityIOS.addEventListener(
* 'change',
* handleFirstReachabilityChange
* );
* ```
*
* ### isConnected
@ -81,89 +140,101 @@ type ReachabilityStateIOS = $Enum<{
* ```
*/
var NetInfo = {};
var _subscriptions = {};
if (RCTReachability) {
// RCTReachability is exposed, so this is an iOS-like environment and we will
// expose reachabilityIOS
var _reachabilitySubscriptions = {};
NetInfo.reachabilityIOS = {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
_reachabilitySubscriptions[handler] = RCTDeviceEventEmitter.addListener(
DEVICE_REACHABILITY_EVENT,
(appStateData) => {
handler(appStateData.network_reachability);
}
);
},
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
if (!_reachabilitySubscriptions[handler]) {
return;
var NetInfo = {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
_subscriptions[handler] = RCTDeviceEventEmitter.addListener(
DEVICE_REACHABILITY_EVENT,
(appStateData) => {
handler(appStateData.network_reachability);
}
_reachabilitySubscriptions[handler].remove();
_reachabilitySubscriptions[handler] = null;
},
);
},
fetch: function(): Promise {
return new Promise((resolve, reject) => {
RCTReachability.getCurrentReachability(
function(resp) {
resolve(resp.network_reachability);
},
reject
);
});
},
};
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
if (!_subscriptions[handler]) {
return;
}
_subscriptions[handler].remove();
_subscriptions[handler] = null;
},
var _isConnectedSubscriptions = {};
fetch: function(): Promise {
return new Promise((resolve, reject) => {
RCTNetInfo.getCurrentReachability(
function(resp) {
resolve(resp.network_reachability);
},
reject
);
});
},
var _iosReachabilityIsConnected = function(
isConnected: {},
isConnectionMetered: {},
};
if (Platform.OS === 'ios') {
var _isConnected = function(
reachability: ReachabilityStateIOS
): bool {
return reachability !== 'none' &&
reachability !== 'unknown';
};
} else if (Platform.OS === 'android') {
var _isConnected = function(
connectionType: ConnectivityStateAndroid
): bool {
return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
};
}
NetInfo.isConnected = {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
_isConnectedSubscriptions[handler] = (reachability) => {
handler(_iosReachabilityIsConnected(reachability));
};
NetInfo.reachabilityIOS.addEventListener(
eventName,
_isConnectedSubscriptions[handler]
);
},
var _isConnectedSubscriptions = {};
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
NetInfo.reachabilityIOS.removeEventListener(
eventName,
_isConnectedSubscriptions[handler]
);
},
NetInfo.isConnected = {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
_isConnectedSubscriptions[handler] = (connection) => {
handler(_isConnected(connection));
};
NetInfo.addEventListener(
eventName,
_isConnectedSubscriptions[handler]
);
},
fetch: function(): Promise {
return NetInfo.reachabilityIOS.fetch().then(
(reachability) => _iosReachabilityIsConnected(reachability)
);
},
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
NetInfo.removeEventListener(
eventName,
_isConnectedSubscriptions[handler]
);
},
fetch: function(): Promise {
return NetInfo.fetch().then(
(connection) => _isConnected(connection)
);
},
};
if (Platform.OS === 'android') {
NetInfo.isConnectionMetered = function(callback): void {
RCTNetInfo.isConnectionMetered((_isMetered) => {
callback(_isMetered);
});
};
}

View File

@ -24,7 +24,6 @@ RCT_EXPORT_MODULE()
*/
RCT_EXPORT_METHOD(queryData:(NSString *)queryType
withQuery:(NSDictionary *)query
queryHash:(__unused NSString *)queryHash
responseSender:(RCTResponseSenderBlock)responseSender)
{
if ([queryType isEqualToString:@"http"]) {
@ -39,34 +38,35 @@ RCT_EXPORT_METHOD(queryData:(NSString *)queryType
// Build data task
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) {
NSHTTPURLResponse *httpResponse = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// Might be a local file request
httpResponse = (NSHTTPURLResponse *)response;
}
// Build response
NSDictionary *responseJSON;
NSArray *responseJSON;
if (connectionError == nil) {
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
NSHTTPURLResponse *httpResponse = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// Might be a local file request
httpResponse = (NSHTTPURLResponse *)response;
}
responseJSON = @{
@"status": @([httpResponse statusCode] ?: 200),
@"responseHeaders": [httpResponse allHeaderFields] ?: @{},
@"responseText": [[NSString alloc] initWithData:data encoding:encoding] ?: @""
};
responseJSON = @[
@(httpResponse.statusCode ?: 200),
httpResponse.allHeaderFields ?: @{},
[[NSString alloc] initWithData:data encoding:encoding] ?: @"",
];
} else {
responseJSON = @{
@"status": @0,
@"responseHeaders": @{},
@"responseText": [connectionError localizedDescription] ?: [NSNull null]
};
responseJSON = @[
@(httpResponse.statusCode),
httpResponse.allHeaderFields ?: @{},
connectionError.localizedDescription ?: [NSNull null],
];
}
// Send response (won't be sent on same thread as caller)
responseSender(@[RCTJSONStringify(responseJSON, NULL)]);
responseSender(responseJSON);
}];

View File

@ -13,8 +13,6 @@
var RCTDataManager = require('NativeModules').DataManager;
var crc32 = require('crc32');
var XMLHttpRequestBase = require('XMLHttpRequestBase');
class XMLHttpRequest extends XMLHttpRequestBase {
@ -28,12 +26,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
data: data,
headers: headers,
},
// TODO: Do we need this? is it used anywhere?
'h' + crc32(method + '|' + url + '|' + data),
(result) => {
result = JSON.parse(result);
this.callback(result.status, result.responseHeaders, result.responseText);
}
this.callback.bind(this)
);
}

View File

@ -59,7 +59,7 @@ var InspectorOverlay = React.createClass({
? 'flex-start'
: 'flex-end';
content.push(<View style={[styles.frame, this.state.frame]} />);
content.push(<View pointerEvents="none" style={[styles.frame, this.state.frame]} />);
content.push(<ElementProperties hierarchy={this.state.hierarchy} />);
}
return (

View File

@ -28,7 +28,6 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, assign) NSWritingDirection writingDirection;
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
- (void)recomputeText;
@end

View File

@ -12,6 +12,8 @@
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTSparseArray.h"
#import "RCTText.h"
#import "RCTUtils.h"
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
@ -19,6 +21,8 @@ NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
@implementation RCTShadowText
{
NSTextStorage *_cachedTextStorage;
CGFloat _cachedTextStorageWidth;
NSAttributedString *_cachedAttributedString;
CGFloat _effectiveLetterSpacing;
}
@ -50,8 +54,35 @@ static css_dim_t RCTMeasure(void *context, float width)
return self;
}
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
parentProperties = [super processUpdatedProperties:applierBlocks
parentProperties:parentProperties];
NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width];
[applierBlocks addObject:^(RCTSparseArray *viewRegistry) {
RCTText *view = viewRegistry[self.reactTag];
view.textStorage = textStorage;
}];
return parentProperties;
}
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
[super applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[self dirtyPropagation];
}
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width
{
if (_cachedTextStorage && width == _cachedTextStorageWidth) {
return _cachedTextStorage;
}
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString];
@ -69,13 +100,23 @@ static css_dim_t RCTMeasure(void *context, float width)
[layoutManager addTextContainer:textContainer];
[layoutManager ensureLayoutForTextContainer:textContainer];
_cachedTextStorage = textStorage;
_cachedTextStorageWidth = width;
return textStorage;
}
- (void)dirtyText
{
[super dirtyText];
_cachedTextStorage = nil;
}
- (void)recomputeText
{
[self attributedString];
[self setTextComputed];
[self dirtyPropagation];
}
- (NSAttributedString *)attributedString

View File

@ -96,12 +96,10 @@ RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
NSTextStorage *textStorage = [shadowView buildTextStorageForWidth:shadowView.frame.size.width];
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
text.textStorage = textStorage;
};
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule BridgeProfiling
* @flow
*/
'use strict';
var GLOBAL = GLOBAL || this;
var BridgeProfiling = {
profile(profileName: String, args?: any) {
if (GLOBAL.__BridgeProfilingIsProfiling) {
if (args) {
try {
args = JSON.stringify(args);
} catch(err) {
args = err.message;
}
}
console.profile(profileName, args);
}
},
profileEnd() {
if (GLOBAL.__BridgeProfilingIsProfiling) {
console.profileEnd();
}
},
};
module.exports = BridgeProfiling;

View File

@ -29,6 +29,7 @@ if (dimensions && dimensions.windowPhysicalPixels) {
width: windowPhysicalPixels.width / windowPhysicalPixels.scale,
height: windowPhysicalPixels.height / windowPhysicalPixels.scale,
scale: windowPhysicalPixels.scale,
fontScale: windowPhysicalPixels.fontScale,
};
// delete so no callers rely on this existing

View File

@ -22,6 +22,6 @@ var GLOBAL = this;
*
* However, we still want to treat ErrorUtils as a module so that other modules
* that use it aren't just using a global variable, so simply export the global
* variable here. ErrorUtils is original defined in a file named error-guard.js.
* variable here. ErrorUtils is originally defined in a file named error-guard.js.
*/
module.exports = GLOBAL.ErrorUtils;

View File

@ -17,6 +17,7 @@ var ReactUpdates = require('ReactUpdates');
var invariant = require('invariant');
var warning = require('warning');
var BridgeProfiling = require('BridgeProfiling');
var JSTimersExecution = require('JSTimersExecution');
var INTERNAL_ERROR = 'Error in MessageQueue implementation';
@ -277,7 +278,9 @@ var MessageQueueMixin = {
if (DEBUG_SPY_MODE) {
console.log('N->JS: Callback#' + cbID + '(' + JSON.stringify(args) + ')');
}
BridgeProfiling.profile('Callback#' + cbID + '(' + JSON.stringify(args) + ')');
cb.apply(scope, args);
BridgeProfiling.profileEnd();
} catch(ie_requires_catch) {
throw ie_requires_catch;
} finally {
@ -311,7 +314,9 @@ var MessageQueueMixin = {
'N->JS: ' + moduleName + '.' + methodName +
'(' + JSON.stringify(params) + ')');
}
BridgeProfiling.profile(moduleName + '.' + methodName + '(' + JSON.stringify(params) + ')');
var ret = jsCall(this._requireFunc(moduleName), methodName, params);
BridgeProfiling.profileEnd();
return ret;
},
@ -330,7 +335,8 @@ var MessageQueueMixin = {
processBatch: function(batch) {
var self = this;
return guardReturn(function () {
BridgeProfiling.profile('MessageQueue.processBatch()');
var flushedQueue = guardReturn(function () {
ReactUpdates.batchedUpdates(function() {
batch.forEach(function(call) {
invariant(
@ -346,8 +352,12 @@ var MessageQueueMixin = {
'Unrecognized method called on BatchedBridge: ' + call.method);
}
});
BridgeProfiling.profile('React.batchedUpdates()');
});
BridgeProfiling.profileEnd();
}, null, this._flushedQueueUnguarded, this);
BridgeProfiling.profileEnd();
return flushedQueue;
},
setLoggingEnabled: function(enabled) {
@ -472,8 +482,10 @@ var MessageQueueMixin = {
},
_flushedQueueUnguarded: function() {
// Call the functions registred via setImmediate
BridgeProfiling.profile('JSTimersExecution.callImmediates()');
// Call the functions registered via setImmediate
JSTimersExecution.callImmediates();
BridgeProfiling.profileEnd();
var currentOutgoingItems = this._outgoingItems;
this._swapAndReinitializeBuffer();

View File

@ -59,6 +59,20 @@ class PixelRatio {
return Dimensions.get('window').scale;
}
/**
* Returns the scaling factor for font sizes. This is the ratio that is used to calculate the
* absolute font size, so any elements that heavily depend on that should use this to do
* calculations.
*
* If a font scale is not set, this returns the device pixel ratio.
*
* Currently this is only implemented on Android and reflects the user preference set in
* Settings > Display > Font size, on iOS it will always return the default pixel ratio.
*/
static getFontScale(): number {
return Dimensions.get('window').fontScale || PixelRatio.get();
}
/**
* Converts a layout size (dp) to pixel size (px).
*

View File

@ -1339,8 +1339,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
* AnyThread
*/
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
RCTBatchedBridge *strongSelf = weakSelf;
@ -1348,13 +1351,17 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
return;
}
id call = @{
@"module": module,
@"method": method,
@"args": args,
@"context": context ?: @0,
};
RCT_IF_DEV(NSNumber *callID = _RCTProfileBeginFlowEvent();)
id call = @{
@"js_args": @{
@"module": module,
@"method": method,
@"args": args,
},
@"context": context ?: @0,
RCT_IF_DEV(@"call_id": callID,)
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
strongSelf->_scheduledCallbacks[args[0]] = call;
} else {
@ -1490,8 +1497,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
return NO;
}
RCTProfileBeginFlowEvent();
__weak RCTBatchedBridge *weakSelf = self;
[self dispatchBlock:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
RCTBatchedBridge *strongSelf = weakSelf;
@ -1525,16 +1534,18 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertJSThread();
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
RCTProfileBeginEvent();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];)
RCTProfileBeginFlowEvent();
[self dispatchBlock:^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent();
[observer didUpdateFrame:frameUpdate];
RCTProfileEndEvent(name, @"objc_call,fps", nil);
} forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]];
}
}
@ -1544,18 +1555,29 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
return [call[@"context"] isEqualToNumber:currentExecutorID];
}]];
RCT_IF_DEV(
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
for (NSDictionary *call in calls) {
_RCTProfileEndFlowEvent(call[@"call_id"]);
}
)
if (calls.count > 0) {
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"processBatch"
arguments:@[calls]
arguments:@[[calls valueForKey:@"js_args"]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
[self.perfStats.jsGraph tick:displayLink.timestamp];
dispatch_async(dispatch_get_main_queue(), ^{
[self.perfStats.jsGraph tick:displayLink.timestamp];
});
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink

View File

@ -42,6 +42,12 @@
#endif
#endif
#if RCT_DEV
#define RCT_IF_DEV(...) __VA_ARGS__
#else
#define RCT_IF_DEV(...)
#endif
/**
* By default, only raise an NSAssertion in debug mode
* (custom assert functions will still be called).

View File

@ -11,11 +11,10 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTSparseArray.h"
static uint64_t RCTGetEventID(id<RCTEvent> event)
static NSNumber *RCTGetEventID(id<RCTEvent> event)
{
return (
return @(
[event.viewTag intValue] |
(((uint64_t)event.eventName.hash & 0xFFFF) << 32) |
(((uint64_t)event.coalescingKey) << 48)
@ -68,7 +67,7 @@ static uint64_t RCTGetEventID(id<RCTEvent> event)
@implementation RCTEventDispatcher
{
RCTSparseArray *_eventQueue;
NSMutableDictionary *_eventQueue;
NSLock *_eventQueueLock;
}
@ -79,7 +78,7 @@ RCT_EXPORT_MODULE()
- (instancetype)init
{
if ((self = [super init])) {
_eventQueue = [[RCTSparseArray alloc] init];
_eventQueue = [[NSMutableDictionary alloc] init];
_eventQueueLock = [[NSLock alloc] init];
}
return self;
@ -139,7 +138,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
[_eventQueueLock lock];
uint64_t eventID = RCTGetEventID(event);
NSNumber *eventID = RCTGetEventID(event);
id<RCTEvent> previousEvent = _eventQueue[eventID];
if (previousEvent) {
@ -176,14 +175,14 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
- (void)didUpdateFrame:(RCTFrameUpdate *)update
{
RCTSparseArray *eventQueue;
NSDictionary *eventQueue;
[_eventQueueLock lock];
eventQueue = _eventQueue;
_eventQueue = [[RCTSparseArray alloc] init];
_eventQueue = [[NSMutableDictionary alloc] init];
[_eventQueueLock unlock];
for (id<RCTEvent> event in eventQueue.allObjects) {
for (id<RCTEvent> event in eventQueue.allValues) {
[self dispatchEvent:event];
}
}

View File

@ -20,8 +20,23 @@
* before before using it.
*/
NSString *const RCTProfileDidStartProfiling;
NSString *const RCTProfileDidEndProfiling;
#if RCT_DEV
#define RCTProfileBeginFlowEvent() \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
NSNumber *__rct_profile_flow_id = _RCTProfileBeginFlowEvent(); \
_Pragma("clang diagnostic pop")
#define RCTProfileEndFlowEvent() \
_RCTProfileEndFlowEvent(__rct_profile_flow_id)
RCT_EXTERN NSNumber *_RCTProfileBeginFlowEvent(void);
RCT_EXTERN void _RCTProfileEndFlowEvent(NSNumber *);
/**
* Returns YES if the profiling information is currently being collected
*/
@ -88,6 +103,12 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *
#else
#define RCTProfileBeginFlowEvent()
#define _RCTProfileBeginFlowEvent() @0
#define RCTProfileEndFlowEvent()
#define _RCTProfileEndFlowEvent()
#define RCTProfileIsProfiling(...) NO
#define RCTProfileInit(...)
#define RCTProfileEnd(...) @""

View File

@ -17,6 +17,9 @@
#import "RCTDefines.h"
#import "RCTUtils.h"
NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling";
#if RCT_DEV
#pragma mark - Prototypes
@ -113,10 +116,16 @@ void RCTProfileInit(void)
RCTProfileSamples: [[NSMutableArray alloc] init],
};
);
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling
object:nil];
}
NSString *RCTProfileEnd(void)
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling
object:nil];
RCTProfileLock(
NSString *log = RCTJSONStringify(RCTProfileInfo, NULL);
RCTProfileEventID = 0;
@ -171,4 +180,32 @@ void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString
);
}
NSNumber *_RCTProfileBeginFlowEvent(void)
{
static NSUInteger flowID = 0;
CHECK(@0);
RCTProfileAddEvent(RCTProfileTraceEvents,
@"name": @"flow",
@"id": @(++flowID),
@"cat": @"flow",
@"ph": @"s",
@"ts": RCTProfileTimestamp(CACurrentMediaTime()),
);
return @(flowID);
}
void _RCTProfileEndFlowEvent(NSNumber *flowID)
{
CHECK();
RCTProfileAddEvent(RCTProfileTraceEvents,
@"name": @"flow",
@"id": flowID,
@"cat": @"flow",
@"ph": @"f",
@"ts": RCTProfileTimestamp(CACurrentMediaTime()),
);
}
#endif

View File

@ -133,22 +133,27 @@ static JSValueRef RCTConsoleProfile(JSContextRef context, JSObjectRef object, JS
profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
}
[profiles addObjectsFromArray:@[profileName, profileID]];
id profileInfo = [NSNull null];
if (argumentCount > 1 && !JSValueIsUndefined(context, arguments[1])) {
profileInfo = @[RCTJSValueToNSString(context, arguments[1])];
}
[profiles addObjectsFromArray:@[profileName, profileID, profileInfo]];
RCTLog(@"Profile '%@' finished.", profileName);
return JSValueMakeUndefined(context);
}
static JSValueRef RCTConsoleProfileEnd(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
{
NSString *profileInfo = [profiles lastObject];
[profiles removeLastObject];
NSNumber *profileID = [profiles lastObject];
[profiles removeLastObject];
NSString *profileName = [profiles lastObject];
[profiles removeLastObject];
_RCTProfileEndEvent(profileID, profileName, @"console", nil);
_RCTProfileEndEvent(profileID, profileName, @"console", profileInfo);
RCTLog(@"Profile '%@' started.", profileName);
return JSValueMakeUndefined(context);
}
@ -244,6 +249,13 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
#if RCT_DEV
[strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"];
[strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"];
for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(toggleProfilingFlag:)
name:event
object:nil];
}
#endif
}];
@ -252,6 +264,21 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
return self;
}
- (void)toggleProfilingFlag:(NSNotification *)notification
{
JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);
bool enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling];
JSStringRef JSName = JSStringCreateWithUTF8CString("__BridgeProfilingIsProfiling");
JSObjectSetProperty(_context.ctx,
globalObject,
JSName,
JSValueMakeBoolean(_context.ctx, enabled),
kJSPropertyAttributeNone,
NULL);
JSStringRelease(JSName);
}
- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name
{
JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);
@ -269,6 +296,10 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
- (void)invalidate
{
#if RCT_DEV
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
[_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO];
}

View File

@ -44,8 +44,7 @@ RCT_EXPORT_METHOD(reportSoftException:(NSString *)message
[_delegate handleSoftJSExceptionWithMessage:message stack:stack];
return;
}
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
// JS already logs the error via console.
}
RCT_EXPORT_METHOD(reportFatalException:(NSString *)message

View File

@ -481,6 +481,10 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
shadowView.newView = NO;
}
// These are blocks to be executed on each view, immediately after
// reactSetFrame: has been called. Note that if reactSetFrame: is not called,
// these won't be called either, so this is not a suitable place to update
// properties that aren't related to layout.
NSMutableArray *updateBlocks = [[NSMutableArray alloc] init];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag];
@ -917,6 +921,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call
- (void)batchDidComplete
{
RCTProfileBeginEvent();
// Gather blocks to be executed now that all view hierarchy manipulations have
// been completed (note that these may still take place before layout has finished)
for (RCTViewManager *manager in _viewManagers.allValues) {
@ -947,6 +952,9 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call
_nextLayoutAnimation = nil;
}
RCTProfileEndEvent(@"[RCTUIManager batchDidComplete]", @"uimanager", @{
@"view_count": @([_viewRegistry count]),
});
[self flushUIBlocks];
}

View File

@ -20,8 +20,7 @@ typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) {
RCTUpdateLifecycleDirtied,
};
// TODO: is this redundact now?
typedef void (^RCTApplierBlock)(RCTSparseArray *);
typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
/**
* ShadowView tree mirrors RCT view tree. Every node is highly stateful.
@ -117,34 +116,48 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
* The applierBlocks set contains RCTApplierBlock functions that must be applied
* on the main thread in order to update the view.
*/
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties;
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties;
/**
* Process the updated properties and apply them to view. Shadow view classes
* that add additional propagating properties should override this method.
*/
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties NS_REQUIRES_SUPER;
/**
* Calculate all views whose frame needs updating after layout has been calculated.
* The viewsWithNewFrame set contains the reactTags of the views that need updating.
*/
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint;
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame
parentConstraint:(CGSize)parentConstraint;
/**
* Recursively apply layout to children.
*/
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER;
/**
* The following are implementation details exposed to subclasses. Do not call them directly
*/
- (void)fillCSSNode:(css_node_t *)node;
- (void)dirtyLayout;
- (void)fillCSSNode:(css_node_t *)node NS_REQUIRES_SUPER;
- (void)dirtyLayout NS_REQUIRES_SUPER;
- (BOOL)isLayoutDirty;
// TODO: is this still needed?
- (void)dirtyPropagation;
- (void)dirtyPropagation NS_REQUIRES_SUPER;
- (BOOL)isPropagationDirty;
// TODO: move this to text node?
- (void)dirtyText;
- (void)dirtyText NS_REQUIRES_SUPER;
- (void)setTextComputed NS_REQUIRES_SUPER;
- (BOOL)isTextDirty;
- (void)setTextComputed;
/**
* Triggers a recalculation of the shadow view's layout.
*/
- (void)updateLayout;
- (void)updateLayout NS_REQUIRES_SUPER;
/**
* Computes the recursive offset, meaning the sum of all descendant offsets -

View File

@ -120,7 +120,9 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
// width = 213.5 - 106.5 = 107
// You'll notice that this is the same width we calculated for the parent view because we've taken its position into account.
- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition
- (void)applyLayoutNode:(css_node_t *)node
viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
if (!node->layout.should_update) {
return;
@ -161,12 +163,19 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
for (int i = 0; i < node->children_count; ++i) {
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
[child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[child applyLayoutNode:node->get_child(node->context, i)
viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition];
}
}
- (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
// TODO: we always refresh all propagated properties when propagation is
// dirtied, but really we should track which properties have changed and
// only update those.
if (!_backgroundColor) {
UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
if (parentBackgroundColor) {
@ -190,14 +199,15 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
return parentProperties;
}
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
return;
}
_propagationLifecycle = RCTUpdateLifecycleComputed;
_lastParentProperties = parentProperties;
NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties];
NSDictionary *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties];
for (RCTShadowView *child in _reactSubviews) {
[child collectUpdatedProperties:applierBlocks parentProperties:nextProps];
}
@ -212,21 +222,19 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
{
CGFloat totalOffsetTop = 0.0;
CGFloat totalOffsetLeft = 0.0;
CGSize size = self.frame.size;
CGPoint offset = CGPointZero;
NSInteger depth = 30; // max depth to search
RCTShadowView *shadowView = self;
while (depth && shadowView && shadowView != ancestor) {
totalOffsetTop += shadowView.frame.origin.y;
totalOffsetLeft += shadowView.frame.origin.x;
offset.x += shadowView.frame.origin.x;
offset.y += shadowView.frame.origin.y;
shadowView = shadowView->_superview;
depth--;
}
if (ancestor != shadowView) {
return CGRectNull;
}
return (CGRect){{totalOffsetLeft, totalOffsetTop}, size};
return (CGRect){offset, self.frame.size};
}
- (instancetype)init

View File

@ -277,7 +277,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
// View has cliping enabled, so we can easily test if it is partially
// or completely within the clipRect, and mount or unmount it accordingly
if (CGRectIntersectsRect(clipRect, view.frame)) {
if (!CGRectIsEmpty(CGRectIntersection(clipRect, view.frame))) {
// View is at least partially visible, so remount it if unmounted
if (view.superview == nil) {

View File

@ -60,9 +60,9 @@
"react-timer-mixin": "^0.13.1",
"react-tools": "0.13.2",
"rebound": "^0.0.12",
"sane": "git://github.com/tadeuzagallo/sane.git#a029f8b04a",
"sane": "tadeuzagallo/sane#a029f8b04a",
"source-map": "0.1.31",
"stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638",
"stacktrace-parser": "frantic/stacktrace-parser#493c5e5638",
"uglify-js": "~2.4.16",
"underscore": "1.7.0",
"worker-farm": "^1.3.1",

View File

@ -10,7 +10,7 @@
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var execFile = require('child_process').execFile;
var http = require('http');
var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware');
@ -172,7 +172,7 @@ function getDevToolsLauncher(options) {
var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui';
var script = 'launchChromeDevTools.applescript';
console.log('Launching Dev Tools...');
exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) {
execFile(path.join(__dirname, script), [debuggerURL], function(err, stdout, stderr) {
if (err) {
console.log('Failed to run ' + script, err);
}