diff --git a/.eslintrc b/.eslintrc
index 84045365c..e1ec93029 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,8 +1,19 @@
{
+ "parser": "babel-eslint",
+
+ "ecmaFeatures": {
+ "jsx": true
+ },
+
"env": {
+ "es6": true,
"jasmine": true,
},
+ "plugins": [
+ "react"
+ ],
+
// Map from global var to bool specifying if it can be redefined
"globals": {
"__DEV__": true,
@@ -36,10 +47,10 @@
},
"rules": {
+ "comma-dangle": 0, // disallow trailing commas in object literals
"no-cond-assign": 1, // disallow assignment in conditional expressions
"no-console": 0, // disallow use of console (off by default in the node environment)
"no-constant-condition": 0, // disallow use of constant expressions in conditions
- "no-comma-dangle": 0, // disallow trailing commas in object literals
"no-control-regex": 1, // disallow control characters in regular expressions
"no-debugger": 1, // disallow use of debugger
"no-dupe-keys": 1, // disallow duplicate keys when creating object literals
@@ -107,6 +118,7 @@
"no-warning-comments": 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default)
"no-with": 1, // disallow use of the with statement
"radix": 1, // require use of the second argument for parseInt() (off by default)
+ "semi-spacing": 1, // require a space after a semi-colon
"vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default)
"wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default)
"yoda": 1, // require or disallow Yoda conditions
@@ -177,7 +189,7 @@
"space-in-parens": 0, // require or disallow spaces inside parentheses (off by default)
"space-infix-ops": 1, // require spaces around operators
"space-return-throw-case": 1, // require a space after return, throw, and case
- "space-unary-word-ops": 1, // require a space around word operators such as typeof (off by default)
+ "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default)
"max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default)
"one-var": 0, // allow just one var statement per function (off by default)
"wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default)
@@ -190,6 +202,22 @@
"max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default)
"max-statements": 0, // specify the maximum number of statement allowed in a function (off by default)
"no-bitwise": 1, // disallow use of bitwise operators (off by default)
- "no-plusplus": 0 // disallow use of unary operators, ++ and -- (off by default)
+ "no-plusplus": 0, // disallow use of unary operators, ++ and -- (off by default)
+
+ "react/display-name": 0,
+ "react/jsx-boolean-value": 0,
+ "react/jsx-quotes": [1, "double", "avoid-escape"],
+ "react/jsx-no-undef": 1,
+ "react/jsx-sort-props": 0,
+ "react/jsx-uses-react": 0,
+ "react/jsx-uses-vars": 1,
+ "react/no-did-mount-set-state": [1, "allow-in-func"],
+ "react/no-did-update-set-state": [1, "allow-in-func"],
+ "react/no-multi-comp": 0,
+ "react/no-unknown-property": 0,
+ "react/prop-types": 0,
+ "react/react-in-jsx-scope": 0,
+ "react/self-closing-comp": 1,
+ "react/wrap-multilines": 0
}
}
diff --git a/.flowconfig b/.flowconfig
index 448732ec8..9953a269f 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -16,13 +16,13 @@
.*/node_modules/react-tools/src/event/EventPropagators.js
# Ignore commoner tests
-.*/node_modules/react-tools/node_modules/commoner/test/.*
+.*/node_modules/commoner/test/.*
# See https://github.com/facebook/flow/issues/442
.*/react-tools/node_modules/commoner/lib/reader.js
# Ignore jest
-.*/react-native/node_modules/jest-cli/.*
+.*/node_modules/jest-cli/.*
# Ignore Website
.*/website/.*
diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js
index 7e2b3df17..e6a9b1078 100644
--- a/Examples/Movies/MovieCell.js
+++ b/Examples/Movies/MovieCell.js
@@ -34,7 +34,10 @@ var MovieCell = React.createClass({
var criticsScore = this.props.movie.ratings.critics_score;
return (
-
+
-
);
}
diff --git a/Examples/Movies/Movies.xcodeproj/project.pbxproj b/Examples/Movies/Movies.xcodeproj/project.pbxproj
index e86674f68..21fc0cb88 100644
--- a/Examples/Movies/Movies.xcodeproj/project.pbxproj
+++ b/Examples/Movies/Movies.xcodeproj/project.pbxproj
@@ -16,6 +16,7 @@
140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14312D241AC3654D00CDC950 /* libRCTLinking.a */; };
14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14A2D4411AC3E41A00CC738A /* libReact.a */; };
58C5726B1AA6239E00CDF9C8 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C5725B1AA6236500CDF9C8 /* libRCTText.a */; };
+ 67C95F201B0E64A30040BCE2 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -54,6 +55,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
+ 67C95F1D1B0E647A0040BCE2 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 3C86DF461ADF2C930047B81A;
+ remoteInfo = RCTWebSocket;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@@ -69,6 +77,7 @@
14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; };
14A2D43C1AC3E41A00CC738A /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = ""; };
587650F61A9EB120008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = SOURCE_ROOT; };
+ 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -76,6 +85,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 67C95F201B0E64A30040BCE2 /* libRCTWebSocket.a in Frameworks */,
14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */,
140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */,
1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */,
@@ -135,6 +145,7 @@
58C571FC1AA6124500CDF9C8 /* Libraries */ = {
isa = PBXGroup;
children = (
+ 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */,
14A2D43C1AC3E41A00CC738A /* React.xcodeproj */,
14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */,
134180151AA91740003F314A /* RCTNetwork.xcodeproj */,
@@ -152,6 +163,14 @@
name = Products;
sourceTree = "";
};
+ 67C95F161B0E647A0040BCE2 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
@@ -228,6 +247,10 @@
ProductGroup = 58C572571AA6236500CDF9C8 /* Products */;
ProjectRef = 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */;
},
+ {
+ ProductGroup = 67C95F161B0E647A0040BCE2 /* Products */;
+ ProjectRef = 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */;
+ },
{
ProductGroup = 14A2D43D1AC3E41A00CC738A /* Products */;
ProjectRef = 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */;
@@ -276,6 +299,13 @@
remoteRef = 58C5725A1AA6236500CDF9C8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
+ 67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTWebSocket.a;
+ remoteRef = 67C95F1D1B0E647A0040BCE2 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js
index e484d0f65..05ab80187 100644
--- a/Examples/Movies/SearchScreen.js
+++ b/Examples/Movies/SearchScreen.js
@@ -237,10 +237,31 @@ var SearchScreen = React.createClass({
return ;
},
- renderRow: function(movie: Object) {
+ renderSeparator: function(
+ sectionID: number | string,
+ rowID: number | string,
+ adjacentRowHighlighted: boolean
+ ) {
+ var style = styles.rowSeparator;
+ if (adjacentRowHighlighted) {
+ style = [style, styles.rowSeparatorHide];
+ }
+ return (
+
+ );
+ },
+
+ renderRow: function(
+ movie: Object,
+ sectionID: number | string,
+ rowID: number | string,
+ highlightRowFunc: (sectionID: ?number | string, rowID: ?number | string) => void,
+ ) {
return (
this.selectMovie(movie)}
+ onHighlight={() => highlightRowFunc(sectionID, rowID)}
+ onUnhighlight={() => highlightRowFunc(null, null)}
movie={movie}
/>
);
@@ -254,6 +275,7 @@ var SearchScreen = React.createClass({
/> :
+ alert('onAccessibilityTap success')}
+ accessible={true}>
+
+ Accessibility normal tap example
+
+
+ alert('onMagicTap success')}
+ accessible={true}>
+
+ Accessibility magic tap example
+
+
+
+
+ Accessibility label example
+
+
+
+
+ Accessibility traits example
+
+
+
+ );
+ },
+});
+
+exports.title = 'AcccessibilityIOS';
+exports.description = 'Interface to show iOS\' accessibility samples';
+exports.examples = [
+ {
+ title: 'Accessibility elements',
+ render(): ReactElement { return ; }
+ },
+];
diff --git a/Examples/UIExplorer/AppStateIOSExample.js b/Examples/UIExplorer/AppStateIOSExample.js
index 9272f9e78..c2a011ceb 100644
--- a/Examples/UIExplorer/AppStateIOSExample.js
+++ b/Examples/UIExplorer/AppStateIOSExample.js
@@ -28,13 +28,19 @@ var AppStateSubscription = React.createClass({
return {
appState: AppStateIOS.currentState,
previousAppStates: [],
+ memoryWarnings: 0,
};
},
componentDidMount: function() {
AppStateIOS.addEventListener('change', this._handleAppStateChange);
+ AppStateIOS.addEventListener('memoryWarning', this._handleMemoryWarning);
},
componentWillUnmount: function() {
AppStateIOS.removeEventListener('change', this._handleAppStateChange);
+ AppStateIOS.removeEventListener('memoryWarning', this._handleMemoryWarning);
+ },
+ _handleMemoryWarning: function() {
+ this.setState({memoryWarnings: this.state.memoryWarnings + 1})
},
_handleAppStateChange: function(appState) {
var previousAppStates = this.state.previousAppStates.slice();
@@ -45,6 +51,13 @@ var AppStateSubscription = React.createClass({
});
},
render() {
+ if (this.props.showMemoryWarnings) {
+ return (
+
+ {this.state.memoryWarnings}
+
+ );
+ }
if (this.props.showCurrentOnly) {
return (
@@ -77,4 +90,9 @@ exports.examples = [
title: 'Previous states:',
render(): ReactElement { return ; }
},
+ {
+ title: 'Memory Warnings',
+ description: "In the simulator, hit Shift+Command+M to simulate a memory warning.",
+ render(): ReactElement { return ; }
+ },
];
diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js
index f49329fae..7789459f8 100644
--- a/Examples/UIExplorer/BorderExample.js
+++ b/Examples/UIExplorer/BorderExample.js
@@ -80,6 +80,13 @@ var styles = StyleSheet.create({
borderTopLeftRadius: 100,
},
+ border7: {
+ borderRadius: 20,
+ },
+ border7_inner: {
+ backgroundColor: 'blue',
+ flex: 1,
+ },
});
exports.title = 'Border';
@@ -134,4 +141,15 @@ exports.examples = [
return ;
}
},
+ {
+ title: 'Custom Borders',
+ description: 'borderRadius & clipping',
+ render() {
+ return (
+
+
+
+ );
+ }
+ },
];
diff --git a/Examples/UIExplorer/TabBarIOSExample.js b/Examples/UIExplorer/TabBarIOSExample.js
index a8f913a07..12ca22e78 100644
--- a/Examples/UIExplorer/TabBarIOSExample.js
+++ b/Examples/UIExplorer/TabBarIOSExample.js
@@ -48,7 +48,9 @@ var TabBarExample = React.createClass({
render: function() {
return (
-
+
"]) {
- return YES;
- }
+ if ([view.accessibilityLabel isEqualToString:@""]) {
+ return YES;
}
return NO;
}];
diff --git a/Libraries/AppStateIOS/AppStateIOS.ios.js b/Libraries/AppStateIOS/AppStateIOS.ios.js
index 48db8415b..e123d23be 100644
--- a/Libraries/AppStateIOS/AppStateIOS.ios.js
+++ b/Libraries/AppStateIOS/AppStateIOS.ios.js
@@ -11,15 +11,18 @@
*/
'use strict';
+var Map = require('Map');
var NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTAppState = NativeModules.AppState;
var logError = require('logError');
+var invariant = require('invariant');
-var DEVICE_APPSTATE_EVENT = 'appStateDidChange';
-
-var _appStateHandlers = {};
+var _eventHandlers = {
+ change: new Map(),
+ memoryWarning: new Map(),
+};
/**
* `AppStateIOS` can tell you if the app is in the foreground or background,
@@ -82,12 +85,23 @@ var AppStateIOS = {
type: string,
handler: Function
) {
- _appStateHandlers[handler] = RCTDeviceEventEmitter.addListener(
- DEVICE_APPSTATE_EVENT,
- (appStateData) => {
- handler(appStateData.app_state);
- }
+ invariant(
+ ['change', 'memoryWarning'].indexOf(type) !== -1,
+ 'Trying to subscribe to unknown event: "%s"', type
);
+ if (type === 'change') {
+ _eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
+ 'appStateDidChange',
+ (appStateData) => {
+ handler(appStateData.app_state);
+ }
+ ));
+ } else if (type === 'memoryWarning') {
+ _eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
+ 'memoryWarning',
+ handler
+ ));
+ }
},
/**
@@ -97,11 +111,15 @@ var AppStateIOS = {
type: string,
handler: Function
) {
- if (!_appStateHandlers[handler]) {
+ invariant(
+ ['change', 'memoryWarning'].indexOf(type) !== -1,
+ 'Trying to remove listener for unknown event: "%s"', type
+ );
+ if (!_eventHandlers[type].has(handler)) {
return;
}
- _appStateHandlers[handler].remove();
- _appStateHandlers[handler] = null;
+ _eventHandlers[type].get(handler).remove();
+ _eventHandlers[type].delete(handler);
},
currentState: (null : ?String),
@@ -109,7 +127,7 @@ var AppStateIOS = {
};
RCTDeviceEventEmitter.addListener(
- DEVICE_APPSTATE_EVENT,
+ 'appStateDidChange',
(appStateData) => {
AppStateIOS.currentState = appStateData.app_state;
}
diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
index 4163b2d78..1910c67ca 100644
--- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
@@ -25,11 +25,22 @@ var TabBarIOS = React.createClass({
propTypes: {
style: View.propTypes.style,
+ /**
+ * Color of the currently selected tab icon
+ */
+ tintColor: React.PropTypes.string,
+ /**
+ * Background color of the tab bar
+ */
+ barTintColor: React.PropTypes.string
},
render: function() {
return (
-
+
{this.props.children}
);
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index b96d8d0d0..533652f65 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -70,6 +70,14 @@ var TouchableHighlight = React.createClass({
*/
underlayColor: React.PropTypes.string,
style: View.propTypes.style,
+ /**
+ * Called immediately after the underlay is shown
+ */
+ onShowUnderlay: React.PropTypes.func,
+ /**
+ * Called immediately after the underlay is hidden
+ */
+ onHideUnderlay: React.PropTypes.func,
},
mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin],
@@ -159,6 +167,7 @@ var TouchableHighlight = React.createClass({
_showUnderlay: function() {
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
+ this.props.onShowUnderlay && this.props.onShowUnderlay();
},
_hideUnderlay: function() {
@@ -170,6 +179,7 @@ var TouchableHighlight = React.createClass({
...INACTIVE_UNDERLAY_PROPS,
style: this.state.underlayStyle,
});
+ this.props.onHideUnderlay && this.props.onHideUnderlay();
}
},
diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index abec1a6e0..17f1e477d 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -108,7 +108,7 @@ var ListView = React.createClass({
* You must provide a renderRow function. If you omit any of the other render
* functions, ListView will simply skip rendering them.
*
- * - renderRow(rowData, sectionID, rowID);
+ * - renderRow(rowData, sectionID, rowID, highlightRow);
* - renderSectionHeader(sectionData, sectionID);
*/
propTypes: {
@@ -116,11 +116,22 @@ var ListView = React.createClass({
dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired,
/**
- * (rowData, sectionID, rowID) => renderable
+ * (sectionID, rowID, adjacentRowHighlighted) => renderable
+ * If provided, a renderable component to be rendered as the separator
+ * below each row but not the last row if there is a section header below.
+ * Take a sectionID and rowID of the row above and whether its adjacent row
+ * is highlighted.
+ */
+ renderSeparator: PropTypes.func,
+ /**
+ * (rowData, sectionID, rowID, highlightRow) => renderable
* Takes a data entry from the data source and its ids and should return
* a renderable component to be rendered as the row. By default the data
* is exactly what was put into the data source, but it's also possible to
- * provide custom extractors.
+ * provide custom extractors. ListView can be notified when a row is
+ * being highlighted by calling highlightRow function. The separators above and
+ * below will be hidden when a row is highlighted. The highlighted state of
+ * a row can be reset by calling highlightRow(null).
*/
renderRow: PropTypes.func.isRequired,
/**
@@ -227,6 +238,7 @@ var ListView = React.createClass({
return {
curRenderedRowsCount: this.props.initialListSize,
prevRenderedRowsCount: 0,
+ highlightedRow: {},
};
},
@@ -256,6 +268,10 @@ var ListView = React.createClass({
}
},
+ onRowHighlighted: function(sectionID, rowID) {
+ this.setState({highlightedRow: {sectionID, rowID}});
+ },
+
render: function() {
var bodyComponents = [];
@@ -305,11 +321,28 @@ var ListView = React.createClass({
null,
dataSource.getRowData(sectionIdx, rowIdx),
sectionID,
- rowID
+ rowID,
+ this.onRowHighlighted
)}
/>;
bodyComponents.push(row);
totalIndex++;
+
+ if (this.props.renderSeparator &&
+ (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length -1)) {
+ var adjacentRowHighlighted =
+ this.state.highlightedRow.sectionID === sectionID && (
+ this.state.highlightedRow.rowID === rowID ||
+ this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]
+ );
+ var separator = this.props.renderSeparator(
+ sectionID,
+ rowID,
+ adjacentRowHighlighted
+ );
+ bodyComponents.push(separator);
+ totalIndex++;
+ }
if (++rowCount === this.state.curRenderedRowsCount) {
break;
}
diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js
index b951d7582..1ebbc70aa 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -332,7 +332,7 @@ var Navigator = React.createClass({
this._subRouteFocus = [];
this.navigatorContext = {
// Actions for child navigators or interceptors:
- setHandlerForRoute: this.setHandlerForRoute,
+ setHandlerForIndex: this.setHandlerForIndex,
request: this.request,
// Contextual utilities
@@ -340,14 +340,13 @@ var Navigator = React.createClass({
getCurrentRoutes: this.getCurrentRoutes,
// `route` is injected by NavigatorStaticContextContainer
- // Contextual nav actions
+ // Contextual nav action
pop: this.requestPop,
- popToRoute: this.requestPopTo,
- // Legacy, imperitive nav actions. Will transition these to contextual actions
jumpBack: this.jumpBack,
jumpForward: this.jumpForward,
jumpTo: this.jumpTo,
+ popToRoute: this.popToRoute,
push: this.push,
replace: this.replace,
replaceAtIndex: this.replaceAtIndex,
@@ -410,8 +409,6 @@ var Navigator = React.createClass({
switch (action) {
case 'pop':
return this._handlePop(arg1);
- case 'popTo':
- return this._handlePopTo(arg1);
case 'push':
return this._handlePush(arg1);
default:
@@ -440,30 +437,13 @@ var Navigator = React.createClass({
return true;
},
- _handlePopTo: function(destRoute) {
- if (destRoute) {
- var hasRoute = this.state.routeStack.indexOf(destRoute) !== -1;
- if (hasRoute) {
- this.popToRoute(destRoute);
- return true;
- } else {
- return false;
- }
- }
- if (this.state.presentedIndex === 0) {
- return false;
- }
- this.pop();
- return true;
- },
-
_handlePush: function(route) {
this.push(route);
return true;
},
- setHandlerForRoute: function(route, handler) {
- this._handlers[this.state.routeStack.indexOf(route)] = handler;
+ setHandlerForIndex: function(index, handler) {
+ this._handlers[index] = handler;
},
componentDidMount: function() {
@@ -1155,17 +1135,13 @@ var Navigator = React.createClass({
this.popToRoute(this.state.routeStack[0]);
},
- _getNumToPopForRoute: function(route) {
+ popToRoute: function(route) {
var indexOfRoute = this.state.routeStack.indexOf(route);
invariant(
indexOfRoute !== -1,
- 'Calling pop to route for a route that doesn\'t exist!'
+ 'Calling popToRoute for a route that doesn\'t exist!'
);
- return this.state.presentedIndex - indexOfRoute;
- },
-
- popToRoute: function(route) {
- var numToPop = this._getNumToPopForRoute(route);
+ var numToPop = this.state.presentedIndex - indexOfRoute;
this._popN(numToPop);
},
@@ -1262,7 +1238,7 @@ var Navigator = React.createClass({
...this.navigatorContext,
route,
setHandler: (handler) => {
- this.navigatorContext.setHandlerForRoute(route, handler);
+ this.navigatorContext.setHandlerForIndex(i, handler);
},
onWillFocus: (childRoute) => {
this._subRouteFocus[i] = childRoute;
diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m
index f21233dbf..0fe5fed75 100644
--- a/Libraries/Geolocation/RCTLocationObserver.m
+++ b/Libraries/Geolocation/RCTLocationObserver.m
@@ -85,7 +85,9 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /
- (void)dealloc
{
- [_timeoutTimer invalidate];
+ if (_timeoutTimer.valid) {
+ [_timeoutTimer invalidate];
+ }
}
@end
@@ -273,6 +275,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
// Fire all queued callbacks
for (RCTLocationRequest *request in _pendingRequests) {
request.successBlock(@[_lastLocationEvent]);
+ [request.timeoutTimer invalidate];
}
[_pendingRequests removeAllObjects];
@@ -311,6 +314,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
// Fire all queued error callbacks
for (RCTLocationRequest *request in _pendingRequests) {
request.errorBlock(@[jsError]);
+ [request.timeoutTimer invalidate];
}
[_pendingRequests removeAllObjects];
diff --git a/Libraries/Inspector.js b/Libraries/Inspector.js
new file mode 100644
index 000000000..5c70be2e8
--- /dev/null
+++ b/Libraries/Inspector.js
@@ -0,0 +1,62 @@
+/**
+ * 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 Inspector
+ */
+'use strict';
+
+var ReactInstanceHandles = require('ReactInstanceHandles');
+var ReactInstanceMap = require('ReactInstanceMap');
+var ReactNativeMount = require('ReactNativeMount');
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
+
+function traverseOwnerTreeUp(hierarchy, instance) {
+ if (instance) {
+ hierarchy.unshift(instance);
+ traverseOwnerTreeUp(hierarchy, instance._currentElement._owner);
+ }
+}
+
+function findInstance(component, targetID) {
+ if (targetID === findRootNodeID(component)) {
+ return component;
+ }
+ if (component._renderedComponent) {
+ return findInstance(component._renderedComponent, targetID);
+ } else {
+ for (var key in component._renderedChildren) {
+ var child = component._renderedChildren[key];
+ if (ReactInstanceHandles.isAncestorIDOf(findRootNodeID(child), targetID)) {
+ var instance = findInstance(child, targetID);
+ if (instance) {
+ return instance;
+ }
+ }
+ }
+ }
+}
+
+function findRootNodeID(component) {
+ var internalInstance = ReactInstanceMap.get(component);
+ return internalInstance ? internalInstance._rootNodeID : component._rootNodeID;
+}
+
+function findInstanceByNativeTag(rootTag, nativeTag) {
+ var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag];
+ var rootInstance = ReactNativeMount._instancesByContainerID[containerID];
+ var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag];
+ return findInstance(rootInstance, targetID);
+}
+
+function getOwnerHierarchy(instance) {
+ var hierarchy = [];
+ traverseOwnerTreeUp(hierarchy, instance);
+ return hierarchy;
+}
+
+module.exports = {findInstanceByNativeTag, getOwnerHierarchy};
diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js
index 99a327b5f..3570e4bf2 100644
--- a/Libraries/Network/XMLHttpRequestBase.js
+++ b/Libraries/Network/XMLHttpRequestBase.js
@@ -23,7 +23,7 @@ class XMLHttpRequestBase {
readyState: number;
responseHeaders: ?Object;
responseText: ?string;
- status: ?string;
+ status: number;
_method: ?string;
_url: ?string;
@@ -43,7 +43,7 @@ class XMLHttpRequestBase {
this.readyState = this.UNSENT;
this.responseHeaders = undefined;
this.responseText = undefined;
- this.status = undefined;
+ this.status = 0;
this._method = null;
this._url = null;
@@ -73,7 +73,7 @@ class XMLHttpRequestBase {
}
setRequestHeader(header: string, value: any): void {
- this._headers[header] = value;
+ this._headers[header.toLowerCase()] = value;
}
open(method: string, url: string, async: ?boolean): void {
@@ -127,7 +127,7 @@ class XMLHttpRequestBase {
this._aborted = true;
}
- callback(status: string, responseHeaders: ?Object, responseText: string): void {
+ callback(status: number, responseHeaders: ?Object, responseText: string): void {
if (this._aborted) {
return;
}
diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay.js
new file mode 100644
index 000000000..a70d3b85d
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay.js
@@ -0,0 +1,112 @@
+/**
+ * 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 InspectorOverlay
+ */
+'use strict';
+
+var Dimensions = require('Dimensions');
+var Inspector = require('Inspector');
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var Text = require('Text');
+var UIManager = require('NativeModules').UIManager;
+var View = require('View');
+
+var InspectorOverlay = React.createClass({
+ getInitialState: function() {
+ return {
+ frame: null,
+ hierarchy: [],
+ };
+ },
+
+ findViewForTouchEvent: function(e) {
+ var {locationX, locationY} = e.nativeEvent.touches[0];
+ UIManager.findSubviewIn(
+ this.props.inspectedViewTag,
+ [locationX, locationY],
+ (nativeViewTag, left, top, width, height) => {
+ var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag);
+ var hierarchy = Inspector.getOwnerHierarchy(instance);
+ this.setState({
+ hierarchy,
+ frame: {left, top, width, height}
+ });
+ }
+ );
+ },
+
+ shouldSetResponser: function(e) {
+ this.findViewForTouchEvent(e);
+ return true;
+ },
+
+ render: function() {
+ var content = [];
+
+ if (this.state.frame) {
+ var distanceToTop = this.state.frame.top;
+ var distanceToBottom = Dimensions.get('window').height -
+ (this.state.frame.top + this.state.frame.height);
+
+ var justifyContent = distanceToTop > distanceToBottom
+ ? 'flex-start'
+ : 'flex-end';
+
+ content.push();
+ content.push();
+ }
+ return (
+
+ {content}
+
+ );
+ }
+});
+
+var ElementProperties = React.createClass({
+ render: function() {
+ var path = this.props.hierarchy.map((instance) => instance.getName()).join(' > ');
+ return (
+
+
+ {path}
+
+
+ );
+ }
+});
+
+var styles = StyleSheet.create({
+ inspector: {
+ backgroundColor: 'rgba(255,255,255,0.8)',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ },
+ frame: {
+ position: 'absolute',
+ backgroundColor: 'rgba(155,155,255,0.3)',
+ },
+ info: {
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
+ padding: 10,
+ },
+ path: {
+ color: 'white',
+ fontSize: 9,
+ }
+});
+
+module.exports = InspectorOverlay;
diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js
index 16052c6fa..1cb24aa71 100644
--- a/Libraries/ReactIOS/renderApplication.ios.js
+++ b/Libraries/ReactIOS/renderApplication.ios.js
@@ -7,17 +7,59 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule renderApplication
- * @flow
*/
'use strict';
+var InspectorOverlay = require('InspectorOverlay');
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var React = require('React');
var StyleSheet = require('StyleSheet');
+var Subscribable = require('Subscribable');
var View = require('View');
var WarningBox = require('WarningBox');
var invariant = require('invariant');
+var AppContainer = React.createClass({
+ mixins: [Subscribable.Mixin],
+
+ getInitialState: function() {
+ return { inspector: null };
+ },
+
+ toggleElementInspector: function() {
+ var inspector = this.state.inspector
+ ? null
+ : ;
+ this.setState({inspector});
+ },
+
+ componentDidMount: function() {
+ this.addListenerOn(
+ RCTDeviceEventEmitter,
+ 'toggleElementInspector',
+ this.toggleElementInspector
+ );
+ },
+
+ render: function() {
+ var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
+ var warningBox = shouldRenderWarningBox ? : null;
+ return (
+
+
+ {this.props.children}
+
+ {warningBox}
+ {this.state.inspector}
+
+ );
+ }
+});
+
function renderApplication(
RootComponent: ReactClass,
initialProps: P,
@@ -27,15 +69,12 @@ function renderApplication(
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
- var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
- var warningBox = shouldRenderWarningBox ? : null;
React.render(
-
+
- {warningBox}
- ,
+ ,
rootTag
);
}
diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js
index c1c3b1376..825739738 100644
--- a/Libraries/ReactIOS/requireNativeComponent.js
+++ b/Libraries/ReactIOS/requireNativeComponent.js
@@ -21,6 +21,7 @@ var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
+var warning = require('warning');
/**
* Used to create React components that directly wrap native component
@@ -42,12 +43,13 @@ function requireNativeComponent(
wrapperComponent: ?Function
): Function {
var viewConfig = RCTUIManager[viewName];
- if (!viewConfig || !viewConfig.nativeProps) {
+ if (!viewConfig || !viewConfig.NativeProps) {
+ warning(false, 'Native component for "%s" does not exist', viewName);
return UnimplementedView;
}
var nativeProps = {
- ...RCTUIManager.RCTView.nativeProps,
- ...viewConfig.nativeProps,
+ ...RCTUIManager.RCTView.NativeProps,
+ ...viewConfig.NativeProps,
};
viewConfig.uiViewClassName = viewName;
viewConfig.validAttributes = {};
diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js
index 5fd3ea1b8..73cb57e9f 100644
--- a/Libraries/ReactIOS/verifyPropTypes.js
+++ b/Libraries/ReactIOS/verifyPropTypes.js
@@ -29,7 +29,7 @@ function verifyPropTypes(
);
}
- var nativeProps = viewConfig.nativeProps;
+ var nativeProps = viewConfig.NativeProps;
for (var prop in nativeProps) {
if (!component.propTypes[prop] &&
!View.propTypes[prop] &&
diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h
index 7a7b44cf8..c93f13184 100644
--- a/Libraries/Text/RCTShadowText.h
+++ b/Libraries/Text/RCTShadowText.h
@@ -14,8 +14,6 @@ extern NSString *const RCTReactTagAttributeName;
@interface RCTShadowText : RCTShadowView
-@property (nonatomic, assign) NSWritingDirection writingDirection;
-@property (nonatomic, strong) UIColor *textBackgroundColor;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, copy) NSString *fontFamily;
@property (nonatomic, assign) CGFloat fontSize;
@@ -24,17 +22,12 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) BOOL isHighlighted;
@property (nonatomic, assign) CGFloat letterSpacing;
@property (nonatomic, assign) CGFloat lineHeight;
-@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
+@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;
-// Not exposed to JS
-@property (nonatomic, strong) UIFont *font;
-@property (nonatomic, assign) NSLineBreakMode truncationMode;
-@property (nonatomic, assign) CGFloat effectiveLetterSpacing;
-
-@property (nonatomic, copy, readonly) NSAttributedString *attributedString;
-@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
-@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
+- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
@end
diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m
index b7e6f997e..dd880340d 100644
--- a/Libraries/Text/RCTShadowText.m
+++ b/Libraries/Text/RCTShadowText.m
@@ -17,62 +17,61 @@
NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName";
NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
+@implementation RCTShadowText
+{
+ NSAttributedString *_cachedAttributedString;
+ CGFloat _effectiveLetterSpacing;
+}
+
static css_dim_t RCTMeasure(void *context, float width)
{
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
-
- NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
- NSTextStorage *previousTextStorage = shadowText.layoutManager.textStorage;
- if (previousTextStorage) {
- [previousTextStorage removeLayoutManager:shadowText.layoutManager];
- }
- [textStorage addLayoutManager:shadowText.layoutManager];
-
- shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
- [shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
-
- CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
-
- [textStorage removeLayoutManager:shadowText.layoutManager];
- if (previousTextStorage) {
- [previousTextStorage addLayoutManager:shadowText.layoutManager];
- }
+ NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width];
+ NSLayoutManager *layoutManager = [textStorage.layoutManagers firstObject];
+ NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
+ CGSize computedSize = [layoutManager usedRectForTextContainer:textContainer].size;
css_dim_t result;
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
- if (shadowText.effectiveLetterSpacing < 0) {
- result.dimensions[CSS_WIDTH] -= shadowText.effectiveLetterSpacing;
+ if (shadowText->_effectiveLetterSpacing < 0) {
+ result.dimensions[CSS_WIDTH] -= shadowText->_effectiveLetterSpacing;
}
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(computedSize.height);
return result;
}
-@implementation RCTShadowText
-{
- NSLayoutManager *_layoutManager;
- NSTextContainer *_textContainer;
- NSAttributedString *_cachedAttributedString;
- UIFont *_font;
-}
-
- (instancetype)init
{
if ((self = [super init])) {
_fontSize = NAN;
_letterSpacing = NAN;
_isHighlighted = NO;
-
- _textContainer = [[NSTextContainer alloc] init];
- _textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
- _textContainer.lineFragmentPadding = 0.0;
-
- _layoutManager = [[NSLayoutManager alloc] init];
- [_layoutManager addTextContainer:_textContainer];
}
-
return self;
}
+- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width
+{
+ NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
+
+ NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString];
+ [textStorage addLayoutManager:layoutManager];
+
+ NSTextContainer *textContainer = [[NSTextContainer alloc] init];
+ textContainer.lineFragmentPadding = 0.0;
+ textContainer.lineBreakMode = _numberOfLines > 0 ? NSLineBreakByTruncatingTail : NSLineBreakByClipping;
+ textContainer.maximumNumberOfLines = _numberOfLines;
+
+ UIEdgeInsets padding = self.paddingAsInsets;
+ width -= (padding.left + padding.right);
+ textContainer.size = (CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX};
+
+ [layoutManager addTextContainer:textContainer];
+ [layoutManager ensureLayoutForTextContainer:textContainer];
+
+ return textStorage;
+}
+
- (NSAttributedString *)attributedString
{
return [self _attributedStringWithFontFamily:nil
@@ -135,8 +134,8 @@ static css_dim_t RCTMeasure(void *context, float width)
[self _addAttribute:NSBackgroundColorAttributeName withValue:self.textBackgroundColor toAttributedString:attributedString];
}
- _font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
- [self _addAttribute:NSFontAttributeName withValue:_font toAttributedString:attributedString];
+ UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
+ [self _addAttribute:NSFontAttributeName withValue:font toAttributedString:attributedString];
[self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString];
[self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString];
[self _setParagraphStyleOnAttributedString:attributedString];
@@ -148,11 +147,6 @@ static css_dim_t RCTMeasure(void *context, float width)
return _cachedAttributedString;
}
-- (UIFont *)font
-{
- return _font ?: [RCTConvert UIFont:nil withFamily:_fontFamily size:@(_fontSize) weight:_fontWeight style:_fontStyle];
-}
-
- (void)_addAttribute:(NSString *)attribute withValue:(id)attributeValue toAttributedString:(NSMutableAttributedString *)attributedString
{
[attributedString enumerateAttribute:attribute inRange:NSMakeRange(0, [attributedString length]) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
@@ -231,38 +225,18 @@ static css_dim_t RCTMeasure(void *context, float width)
[self dirtyText]; \
}
-RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *);
-RCT_TEXT_PROPERTY(Color, _color, UIColor *);
-RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *);
-RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat);
-RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *);
-RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat);
-RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
-RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
-RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
-RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
-RCT_TEXT_PROPERTY(Font, _font, UIFont *);
-
-- (NSLineBreakMode)truncationMode
-{
- return _textContainer.lineBreakMode;
-}
-
-- (void)setTruncationMode:(NSLineBreakMode)truncationMode
-{
- _textContainer.lineBreakMode = truncationMode;
- [self dirtyText];
-}
-
-- (NSUInteger)maximumNumberOfLines
-{
- return _textContainer.maximumNumberOfLines;
-}
-
-- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
-{
- _textContainer.maximumNumberOfLines = maximumNumberOfLines;
- [self dirtyText];
-}
+RCT_TEXT_PROPERTY(Color, _color, UIColor *)
+RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *)
+RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat)
+RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *)
+RCT_TEXT_PROPERTY(FontStyle, _fontStyle, NSString *)
+RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL)
+RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
+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
diff --git a/Libraries/Text/RCTText.h b/Libraries/Text/RCTText.h
index fe7803eb5..487954f8a 100644
--- a/Libraries/Text/RCTText.h
+++ b/Libraries/Text/RCTText.h
@@ -11,9 +11,7 @@
@interface RCTText : UIView
-@property (nonatomic, strong) NSLayoutManager *layoutManager;
-@property (nonatomic, strong) NSTextContainer *textContainer;
-@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, assign) UIEdgeInsets contentInset;
+@property (nonatomic, strong) NSTextStorage *textStorage;
@end
diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m
index b4fcc6951..cdb09d4ff 100644
--- a/Libraries/Text/RCTText.m
+++ b/Libraries/Text/RCTText.m
@@ -15,9 +15,7 @@
@implementation RCTText
{
- NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
- NSTextContainer *_textContainer;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -31,103 +29,42 @@
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
-
return self;
}
-- (NSAttributedString *)attributedText
+- (void)setTextStorage:(NSTextStorage *)textStorage
{
- return [_textStorage copy];
-}
-
-- (void)setAttributedText:(NSAttributedString *)attributedText
-{
- for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
- [_textStorage removeLayoutManager:existingLayoutManager];
- }
-
- _textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
-
- if (_layoutManager) {
- [_textStorage addLayoutManager:_layoutManager];
- }
-
+ _textStorage = textStorage;
[self setNeedsDisplay];
}
-- (void)setTextContainer:(NSTextContainer *)textContainer
-{
- if ([_textContainer isEqual:textContainer]) {
- return;
- }
-
- _textContainer = textContainer;
-
- for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
- [_layoutManager removeTextContainerAtIndex:i];
- }
-
- if (_textContainer) {
- [_layoutManager addTextContainer:_textContainer];
- }
-
- [self setNeedsDisplay];
-}
-
-- (void)setLayoutManager:(NSLayoutManager *)layoutManager
-{
- if ([_layoutManager isEqual:layoutManager]) {
- return;
- }
-
- _layoutManager = layoutManager;
-
- for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
- [_textStorage removeLayoutManager:existingLayoutManager];
- }
-
- if (_layoutManager) {
- [_textStorage addLayoutManager:_layoutManager];
- }
-
- [self setNeedsDisplay];
-}
-
-- (CGRect)textFrame
-{
- return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
-}
-
- (void)drawRect:(CGRect)rect
{
- CGRect textFrame = [self textFrame];
-
- // We reset the text container size every time because RCTShadowText's
- // RCTMeasure overrides it. The header comment for `size` says that a height
- // of 0.0 should be enough, but it isn't.
- _textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX);
-
- NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
- [_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
- [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
+ NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
+ NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
+ CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, _contentInset);
+ NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
+ [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
+ [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
- CGFloat fraction;
- NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point
- inTextContainer:_textContainer
- fractionOfDistanceBetweenInsertionPoints:&fraction];
+ NSNumber *reactTag = self.reactTag;
- NSNumber *reactTag = nil;
+ CGFloat fraction;
+ NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
+ NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
+ NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
+ inTextContainer:textContainer
+ fractionOfDistanceBetweenInsertionPoints:&fraction];
// If the point is not before (fraction == 0.0) the first character and not
// after (fraction == 1.0) the last character, then the attribute is valid.
if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && (fraction < 1 || characterIndex < _textStorage.length - 1)) {
reactTag = [_textStorage attribute:RCTReactTagAttributeName atIndex:characterIndex effectiveRange:NULL];
}
-
- return reactTag ?: self.reactTag;
+ return reactTag;
}
#pragma mark - Accessibility
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index b2832a800..3dbb2ed53 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -47,24 +47,11 @@ RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)
-RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, UIColor, RCTShadowText)
-{
- view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
- view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
-}
-RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
-{
- NSLineBreakMode truncationMode = NSLineBreakByClipping;
- view.maximumNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maximumNumberOfLines;
- if (view.maximumNumberOfLines > 0) {
- truncationMode = NSLineBreakByTruncatingTail;
- }
- view.truncationMode = truncationMode;
-}
+RCT_REMAP_SHADOW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
+RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
@@ -81,7 +68,7 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
continue;
}
- RCTSparseArray *reactTaggedAttributedStrings = [[RCTSparseArray alloc] init];
+ RCTSparseArray *reactTaggedTextStorage = [[RCTSparseArray alloc] init];
NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
for (NSInteger i = 0; i < [queue count]; i++) {
RCTShadowView *shadowView = queue[i];
@@ -89,9 +76,11 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)shadowView;
- reactTaggedAttributedStrings[shadowText.reactTag] = [shadowText attributedString];
+ NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:shadowView.frame.size.width];
+ reactTaggedTextStorage[shadowText.reactTag] = textStorage;
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
- RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]);
+ RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'",
+ [(RCTShadowRawText *)shadowView text]);
} else {
for (RCTShadowView *child in [shadowView reactSubviews]) {
if ([child isTextDirty]) {
@@ -104,9 +93,9 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
}
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
- [reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) {
+ [reactTaggedTextStorage enumerateObjectsUsingBlock:^(NSTextStorage *textStorage, NSNumber *reactTag, BOOL *stop) {
RCTText *text = viewRegistry[reactTag];
- text.attributedText = attributedString;
+ text.textStorage = textStorage;
}];
}];
}
@@ -126,8 +115,6 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
- text.layoutManager = shadowView.layoutManager;
- text.textContainer = shadowView.textContainer;
};
}
diff --git a/Libraries/vendor/crypto/crc32.js b/Libraries/vendor/crypto/crc32.js
index 1ded3fddf..540d60aa8 100644
--- a/Libraries/vendor/crypto/crc32.js
+++ b/Libraries/vendor/crypto/crc32.js
@@ -1,5 +1,5 @@
/**
- * @generated SignedSource<<9bce659a43d6f6115b20a18f6c995d8a>>
+ * @generated SignedSource<<77bdeb858138636c96c405d64b6be55c>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
@@ -20,57 +20,73 @@
/* jslint bitwise: true */
/**
- * Copyright (c) 2006 Andrea Ercolino
- * http://www.opensource.org/licenses/mit-license.php
+ * Modified from the original for performance improvements.
+ *
+ * @see http://create.stephan-brumme.com/crc32/
+ * @see http://stackoverflow.com/questions/18638900/
+ * @copyright 2006 Andrea Ercolino
+ * @license MIT
*/
-var table = '00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 ' +
- '9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 ' +
- '90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 ' +
- '83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 ' +
- '8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD ' +
- 'A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ' +
- 'ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 ' +
- 'B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB ' +
- 'B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 ' +
- 'E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 ' +
- 'E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 ' +
- 'F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 ' +
- 'FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D ' +
- 'D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F ' +
- 'DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 ' +
- 'CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B ' +
- 'C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 ' +
- '73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 ' +
- '7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 ' +
- '6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 ' +
- '60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD ' +
- '48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF ' +
- '4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 ' +
- '5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B ' +
- '5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 ' +
- '05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 ' +
- '0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 ' +
- '18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 ' +
- '166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D ' +
- '3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F ' +
- '30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 ' +
- '23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B ' +
- '2D02EF8D';
+
+var table = [
+ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
+ 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
+ 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
+ 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+ 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
+ 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
+ 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
+ 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+ 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
+ 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
+ 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
+ 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+ 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
+ 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
+ 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
+ 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+ 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
+ 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
+ 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
+ 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+ 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
+ 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
+ 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
+ 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+ 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
+ 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
+ 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
+ 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+ 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
+ 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
+ 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
+ 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+ 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
+ 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
+ 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
+ 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+ 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
+ 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
+ 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
+ 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+ 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
+ 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
+ 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+];
+
+if (global.Int32Array !== undefined) {
+ table = new Int32Array(table);
+}
/**
* @returns Number
*/
function crc32(str) {
- var crc = 0;
- var n = 0;
- var x = 0;
- crc = crc ^ (-1);
- for (var i = 0, iTop = str.length; i < iTop; i++) {
- n = (crc ^ str.charCodeAt(i)) & 0xFF;
- x = "0x" + table.substr(n * 9, 8);
- crc = (crc >>> 8) ^ x;
+ var crc = -1;
+ for (var i = 0, len = str.length; i < len; i++) {
+ crc = (crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xFF];
}
- return crc ^ (-1);
+ return ~crc;
}
module.exports = crc32;
diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h
index b0a3c5c52..1fc5b9d32 100644
--- a/React/Base/RCTAssert.h
+++ b/React/Base/RCTAssert.h
@@ -62,3 +62,31 @@ RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void);
* assert info to an extra service without changing the default behavior.
*/
RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction);
+
+/**
+ * Get the current thread's name (or the current queue, if in debug mode)
+ */
+RCT_EXTERN NSString *RCTCurrentThreadName(void);
+
+/**
+ * Convenience macro to assert which thread is currently running (DEBUG mode only)
+ */
+#if DEBUG
+
+#define RCTAssertThread(thread, format...) \
+_Pragma("clang diagnostic push") \
+_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \
+RCTAssert( \
+ [(id)thread isKindOfClass:[NSString class]] ? \
+ [RCTCurrentThreadName() isEqualToString:(NSString *)thread] : \
+ [(id)thread isKindOfClass:[NSThread class]] ? \
+ [NSThread currentThread] == (NSThread *)thread : \
+ dispatch_get_current_queue() == (dispatch_queue_t)thread, \
+ format); \
+_Pragma("clang diagnostic pop")
+
+#else
+
+#define RCTAssertThread(thread, format...)
+
+#endif
diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m
index 86d71cd80..41369406c 100644
--- a/React/Base/RCTAssert.m
+++ b/React/Base/RCTAssert.m
@@ -60,3 +60,20 @@ void RCTAddAssertFunction(RCTAssertFunction assertFunction)
RCTCurrentAssertFunction = assertFunction;
}
}
+
+NSString *RCTCurrentThreadName(void)
+{
+ NSThread *thread = [NSThread currentThread];
+ NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
+ if (threadName.length == 0) {
+#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
+#pragma clang diagnostic pop
+#else
+ threadName = [NSString stringWithFormat:@"%p", thread];
+#endif
+ }
+ return threadName;
+}
diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h
index 7c8af00cc..5f84ed9da 100644
--- a/React/Base/RCTBridge.h
+++ b/React/Base/RCTBridge.h
@@ -95,6 +95,11 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
/**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
* higher-level interface for sending UI events such as touches and text input.
+ *
+ * NOTE: RCTEventDispatcher is now a bridge module, this is implemented as a
+ * category but remains declared in the bridge to avoid breaking changes
+ *
+ * To be moved.
*/
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
index 523333aac..47c7f5942 100644
--- a/React/Base/RCTBridge.m
+++ b/React/Base/RCTBridge.m
@@ -18,7 +18,6 @@
#import "RCTContextExecutor.h"
#import "RCTConvert.h"
-#import "RCTEventDispatcher.h"
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
@@ -211,7 +210,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
@property (nonatomic, strong) RCTBridgeModuleProviderBlock moduleProvider;
-@property (nonatomic, strong, readwrite) RCTEventDispatcher *eventDispatcher;
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
@@ -875,11 +873,6 @@ static id _latestJSExecutor;
return _batchedBridge.modules;
}
-- (RCTEventDispatcher *)eventDispatcher
-{
- return _eventDispatcher ?: _batchedBridge.eventDispatcher;
-}
-
#define RCT_INNER_BRIDGE_ONLY(...) \
- (void)__VA_ARGS__ \
{ \
@@ -943,11 +936,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_latestJSExecutor = _javaScriptExecutor;
- /**
- * Setup event dispatcher before initializing modules to allow init calls
- */
- self.eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
-
/**
* Initialize and register bridge modules *before* adding the display link
* so we don't have threading issues
diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m
index 421380e5b..2416e974c 100644
--- a/React/Base/RCTDevMenu.m
+++ b/React/Base/RCTDevMenu.m
@@ -11,6 +11,7 @@
#import "RCTBridge.h"
#import "RCTDefines.h"
+#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
@@ -241,6 +242,8 @@ RCT_EXPORT_METHOD(show)
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
+ [actionSheet addButtonWithTitle:@"Inspect Element"];
+
if (_liveReloadURL) {
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
@@ -300,10 +303,14 @@ RCT_EXPORT_METHOD(reload)
break;
}
case 4: {
- self.liveReloadEnabled = !_liveReloadEnabled;
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
break;
}
case 5: {
+ self.liveReloadEnabled = !_liveReloadEnabled;
+ break;
+ }
+ case 6: {
self.profilingEnabled = !_profilingEnabled;
break;
}
diff --git a/React/Base/RCTEventDispatcher.h b/React/Base/RCTEventDispatcher.h
index 15cb18021..5576df64f 100644
--- a/React/Base/RCTEventDispatcher.h
+++ b/React/Base/RCTEventDispatcher.h
@@ -9,7 +9,7 @@
#import
-@class RCTBridge;
+#import "RCTBridge.h"
typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeFocus,
@@ -28,14 +28,36 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
RCTScrollEventTypeEndAnimation,
};
+@protocol RCTEvent
+
+@required
+
+@property (nonatomic, strong, readonly) NSNumber *viewTag;
+@property (nonatomic, copy, readonly) NSString *eventName;
+@property (nonatomic, copy, readonly) NSDictionary *body;
+@property (nonatomic, assign, readonly) uint16_t coalescingKey;
+
+- (BOOL)canCoalesce;
+- (id)coalesceWithEvent:(id)newEvent;
+
++ (NSString *)moduleDotMethod;
+
+@end
+
+@interface RCTBaseEvent : NSObject
+
+- (instancetype)initWithViewTag:(NSNumber *)viewTag
+ eventName:(NSString *)eventName
+ body:(NSDictionary *)body NS_DESIGNATED_INITIALIZER;
+
+@end
+
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
* provides some convenience methods for generating event calls.
*/
@interface RCTEventDispatcher : NSObject
-- (instancetype)initWithBridge:(RCTBridge *)bridge;
-
/**
* Send an application-specific event that does not relate to a specific
* view, e.g. a navigation or data update notification.
@@ -61,13 +83,6 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
reactTag:(NSNumber *)reactTag
text:(NSString *)text;
-/**
- * Send a scroll event.
- * (You can send a fake scroll event by passing nil for scrollView).
- */
-- (void)sendScrollEventWithType:(RCTScrollEventType)type
- reactTag:(NSNumber *)reactTag
- scrollView:(UIScrollView *)scrollView
- userData:(NSDictionary *)userData;
+- (void)sendEvent:(id)event;
@end
diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m
index 8487556e5..e6ed698d9 100644
--- a/React/Base/RCTEventDispatcher.m
+++ b/React/Base/RCTEventDispatcher.m
@@ -11,16 +11,76 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
+#import "RCTSparseArray.h"
+
+static uint64_t RCTGetEventID(id event)
+{
+ return (
+ [event.viewTag intValue] |
+ (((uint64_t)event.eventName.hash & 0xFFFF) << 32) |
+ (((uint64_t)event.coalescingKey) << 48)
+ );
+}
+
+@implementation RCTBaseEvent
+
+@synthesize viewTag = _viewTag;
+@synthesize eventName = _eventName;
+@synthesize body = _body;
+
+- (instancetype)initWithViewTag:(NSNumber *)viewTag
+ eventName:(NSString *)eventName
+ body:(NSDictionary *)body
+{
+ if ((self = [super init])) {
+ _viewTag = viewTag;
+ _eventName = eventName;
+ _body = body;
+ }
+ return self;
+}
+
+- (uint16_t)coalescingKey
+{
+ return 0;
+}
+
+- (BOOL)canCoalesce
+{
+ return YES;
+}
+
+- (id)coalesceWithEvent:(id)newEvent
+{
+ return newEvent;
+}
+
++ (NSString *)moduleDotMethod
+{
+ return nil;
+}
+
+@end
+
+@interface RCTEventDispatcher()
+
+@end
@implementation RCTEventDispatcher
{
- RCTBridge __weak *_bridge;
+ RCTSparseArray *_eventQueue;
+ NSLock *_eventQueueLock;
}
-- (instancetype)initWithBridge:(RCTBridge *)bridge
+@synthesize bridge = _bridge;
+
+RCT_EXPORT_MODULE()
+
+- (instancetype)init
{
if ((self = [super init])) {
- _bridge = bridge;
+ _eventQueue = [[RCTSparseArray alloc] init];
+ _eventQueueLock = [[NSLock alloc] init];
}
return self;
}
@@ -70,58 +130,71 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
}];
}
-/**
- * TODO: throttling
- * NOTE: the old system used a per-scrollview throttling
- * which would be fairly easy to re-implement if needed,
- * but this is non-optimal as it leads to degradation in
- * scroll responsiveness. A better solution would be to
- * coalesce multiple scroll events into a single batch.
- */
-- (void)sendScrollEventWithType:(RCTScrollEventType)type
- reactTag:(NSNumber *)reactTag
- scrollView:(UIScrollView *)scrollView
- userData:(NSDictionary *)userData
+- (void)sendEvent:(id)event
{
- static NSString *events[] = {
- @"topScrollBeginDrag",
- @"topScroll",
- @"topScrollEndDrag",
- @"topMomentumScrollBegin",
- @"topMomentumScrollEnd",
- @"topScrollAnimationEnd",
- };
-
- NSDictionary *body = @{
- @"contentOffset": @{
- @"x": @(scrollView.contentOffset.x),
- @"y": @(scrollView.contentOffset.y)
- },
- @"contentInset": @{
- @"top": @(scrollView.contentInset.top),
- @"left": @(scrollView.contentInset.left),
- @"bottom": @(scrollView.contentInset.bottom),
- @"right": @(scrollView.contentInset.right)
- },
- @"contentSize": @{
- @"width": @(scrollView.contentSize.width),
- @"height": @(scrollView.contentSize.height)
- },
- @"layoutMeasurement": @{
- @"width": @(scrollView.frame.size.width),
- @"height": @(scrollView.frame.size.height)
- },
- @"zoomScale": @(scrollView.zoomScale ?: 1),
- @"target": reactTag
- };
-
- if (userData) {
- NSMutableDictionary *mutableBody = [body mutableCopy];
- [mutableBody addEntriesFromDictionary:userData];
- body = mutableBody;
+ if (!event.canCoalesce) {
+ [self dispatchEvent:event];
+ return;
}
- [self sendInputEventWithName:events[type] body:body];
+ [_eventQueueLock lock];
+
+ uint64_t eventID = RCTGetEventID(event);
+ id previousEvent = _eventQueue[eventID];
+
+ if (previousEvent) {
+ event = [previousEvent coalesceWithEvent:event];
+ }
+
+ _eventQueue[eventID] = event;
+
+ [_eventQueueLock unlock];
+}
+
+- (void)dispatchEvent:(id)event
+{
+ NSMutableArray *arguments = [[NSMutableArray alloc] init];
+
+ if (event.viewTag) {
+ [arguments addObject:event.viewTag];
+ }
+
+ [arguments addObject:event.eventName];
+
+ if (event.body) {
+ [arguments addObject:event.body];
+ }
+
+ [_bridge enqueueJSCall:[[event class] moduleDotMethod]
+ args:arguments];
+}
+
+- (dispatch_queue_t)methodQueue
+{
+ return RCTJSThread;
+}
+
+- (void)didUpdateFrame:(RCTFrameUpdate *)update
+{
+ RCTSparseArray *eventQueue;
+
+ [_eventQueueLock lock];
+ eventQueue = _eventQueue;
+ _eventQueue = [[RCTSparseArray alloc] init];
+ [_eventQueueLock unlock];
+
+ for (id event in eventQueue.allObjects) {
+ [self dispatchEvent:event];
+ }
+}
+
+@end
+
+@implementation RCTBridge (RCTEventDispatcher)
+
+- (RCTEventDispatcher *)eventDispatcher
+{
+ return self.modules[RCTBridgeModuleNameForClass([RCTEventDispatcher class])];
}
@end
diff --git a/React/Base/RCTLog.h b/React/Base/RCTLog.h
index 75cbe722e..5dead10be 100644
--- a/React/Base/RCTLog.h
+++ b/React/Base/RCTLog.h
@@ -42,18 +42,12 @@ typedef void (^RCTLogFunction)(
NSString *message
);
-/**
- * Get a given thread's name (or the current queue, if in debug mode)
- */
-RCT_EXTERN NSString *RCTThreadName(NSThread *);
-
/**
* A method to generate a string from a collection of log data. To omit any
* particular data from the log, just pass nil or zero for the argument.
*/
RCT_EXTERN NSString *RCTFormatLog(
NSDate *timestamp,
- NSThread *thread,
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m
index fb70fe6d3..d99bb4fe1 100644
--- a/React/Base/RCTLog.m
+++ b/React/Base/RCTLog.m
@@ -53,7 +53,7 @@ RCTLogFunction RCTDefaultLogFunction = ^(
)
{
NSString *log = RCTFormatLog(
- [NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
+ [NSDate date], level, fileName, lineNumber, message
);
fprintf(stderr, "%s\n", log.UTF8String);
fflush(stderr);
@@ -99,25 +99,8 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
[prefixStack removeLastObject];
}
-NSString *RCTThreadName(NSThread *thread)
-{
- NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
- if (threadName.length == 0) {
-#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
-#pragma clang diagnostic pop
-#else
- threadName = [NSString stringWithFormat:@"%p", thread];
-#endif
- }
- return threadName;
-}
-
NSString *RCTFormatLog(
NSDate *timestamp,
- NSThread *thread,
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
@@ -137,9 +120,9 @@ NSString *RCTFormatLog(
if (level) {
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
}
- if (thread) {
- [log appendFormat:@"[tid:%@]", RCTThreadName(thread)];
- }
+
+ [log appendFormat:@"[tid:%@]", RCTCurrentThreadName()];
+
if (fileName) {
fileName = [fileName lastPathComponent];
if (lineNumber) {
diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m
index 19c6900c7..29e606e86 100644
--- a/React/Base/RCTProfile.m
+++ b/React/Base/RCTProfile.m
@@ -13,8 +13,8 @@
#import
+#import "RCTAssert.h"
#import "RCTDefines.h"
-#import "RCTLog.h"
#import "RCTUtils.h"
#if RCT_DEV
@@ -43,7 +43,7 @@ NSLock *_RCTProfileLock;
#define RCTProfileAddEvent(type, props...) \
[RCTProfileInfo[type] addObject:@{ \
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
- @"tid": RCTThreadName([NSThread currentThread]), \
+ @"tid": RCTCurrentThreadName(), \
props \
}];
diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m
index 980d253d3..e9a8170eb 100644
--- a/React/Base/RCTRootView.m
+++ b/React/Base/RCTRootView.m
@@ -51,7 +51,7 @@
RCTRootContentView *_contentView;
}
- - (instancetype)initWithBridge:(RCTBridge *)bridge
+- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
{
RCTAssertMainThread();
@@ -87,6 +87,12 @@
return [self initWithBridge:bridge moduleName:moduleName];
}
+- (void)setBackgroundColor:(UIColor *)backgroundColor
+{
+ super.backgroundColor = backgroundColor;
+ _contentView.backgroundColor = backgroundColor;
+}
+
- (UIViewController *)backingViewController
{
return _backingViewController ?: [super backingViewController];
@@ -100,7 +106,6 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
-
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
@@ -124,6 +129,7 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
+ _contentView.backgroundColor = self.backgroundColor;
[self addSubview:_contentView];
NSString *moduleName = _moduleName ?: @"";
@@ -172,6 +178,7 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
{
__weak RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
+ UIColor *_backgroundColor;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -181,18 +188,32 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
_bridge = bridge;
[self setUp];
self.frame = frame;
+ self.layer.backgroundColor = NULL;
}
return self;
}
- (void)setFrame:(CGRect)frame
{
- [super setFrame:frame];
+ super.frame = frame;
if (self.reactTag && _bridge.isValid) {
- [_bridge.uiManager setFrame:self.bounds forRootView:self];
+ [_bridge.uiManager setFrame:frame forRootView:self];
}
}
+- (void)setBackgroundColor:(UIColor *)backgroundColor
+{
+ _backgroundColor = backgroundColor;
+ if (self.reactTag && _bridge.isValid) {
+ [_bridge.uiManager setBackgroundColor:backgroundColor forRootView:self];
+ }
+}
+
+- (UIColor *)backgroundColor
+{
+ return _backgroundColor;
+}
+
- (void)setUp
{
/**
diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m
index ab5efe5a6..09cfd758f 100644
--- a/React/Executors/RCTContextExecutor.m
+++ b/React/Executors/RCTContextExecutor.m
@@ -66,6 +66,7 @@
{
RCTJavaScriptContext *_context;
NSThread *_javaScriptThread;
+ JSValueRef _undefined;
}
/**
@@ -237,6 +238,9 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
JSContextGroupRelease(group);
}
+ // Constant value used for comparison
+ _undefined = JSValueMakeUndefined(ctx);
+
strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
[strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[strongSelf _addNativeHook:RCTNoop withName:"noop"];
@@ -291,21 +295,77 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
return;
}
NSError *error;
- NSString *argsString = RCTJSONStringify(arguments, &error);
+ NSString *argsString = (arguments.count == 1) ? RCTJSONStringify(arguments[0], &error) : RCTJSONStringify(arguments, &error);
if (!argsString) {
RCTLogError(@"Cannot convert argument to string: %@", error);
onComplete(nil, error);
return;
}
- NSString *execString = [NSString stringWithFormat:@"require('%@').%@.apply(null, %@);", name, method, argsString];
- JSValueRef jsError = NULL;
- JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)execString);
- JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, NULL, 0, &jsError);
- JSStringRelease(execJSString);
+ JSValueRef errorJSRef = NULL;
+ JSValueRef resultJSRef = NULL;
+ JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx);
+ JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx);
- if (!result) {
- onComplete(nil, RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError));
+ // get require
+ JSStringRef requireNameJSStringRef = JSStringCreateWithUTF8CString("require");
+ JSValueRef requireJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, requireNameJSStringRef, &errorJSRef);
+ JSStringRelease(requireNameJSStringRef);
+
+ if (requireJSRef != NULL && requireJSRef != _undefined && errorJSRef == NULL) {
+
+ // get module
+ JSStringRef moduleNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)name);
+ JSValueRef moduleNameJSRef = JSValueMakeString(contextJSRef, moduleNameJSStringRef);
+ JSValueRef moduleJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)requireJSRef, NULL, 1, (const JSValueRef *)&moduleNameJSRef, &errorJSRef);
+ JSStringRelease(moduleNameJSStringRef);
+
+ if (moduleJSRef != NULL && errorJSRef == NULL) {
+
+ // get method
+ JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
+ JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
+ JSStringRelease(methodNameJSStringRef);
+
+ if (methodJSRef != NULL && errorJSRef == NULL) {
+
+ // direct method invoke with no arguments
+ if (arguments.count == 0) {
+ resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
+ }
+
+ // direct method invoke with 1 argument
+ else if(arguments.count == 1) {
+ JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
+ JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
+ resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef);
+ JSStringRelease(argsJSStringRef);
+
+ } else {
+ // apply invoke with array of arguments
+ JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply");
+ JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef);
+ JSStringRelease(applyNameJSStringRef);
+
+ if (applyJSRef != NULL && errorJSRef == NULL) {
+ // invoke apply
+ JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
+ JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
+
+ JSValueRef args[2];
+ args[0] = JSValueMakeNull(contextJSRef);
+ args[1] = argsJSRef;
+
+ resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef);
+ JSStringRelease(argsJSStringRef);
+ }
+ }
+ }
+ }
+ }
+
+ if (!resultJSRef) {
+ onComplete(nil, RCTNSErrorFromJSError(contextJSRef, errorJSRef));
return;
}
@@ -315,8 +375,8 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
id objcValue;
// We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
// to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
- if (!JSValueIsNull(strongSelf->_context.ctx, result)) {
- JSStringRef jsJSONString = JSValueCreateJSONString(strongSelf->_context.ctx, result, 0, nil);
+ if (!JSValueIsNull(contextJSRef, resultJSRef)) {
+ JSStringRef jsJSONString = JSValueCreateJSONString(contextJSRef, resultJSRef, 0, nil);
if (jsJSONString) {
NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString);
JSStringRelease(jsJSONString);
diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m
index 9743e5042..8c46655c6 100644
--- a/React/Modules/RCTAppState.m
+++ b/React/Modules/RCTAppState.m
@@ -53,11 +53,21 @@ RCT_EXPORT_MODULE()
selector:@selector(handleAppStateDidChange)
name:name
object:nil];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleMemoryWarning)
+ name:UIApplicationDidReceiveMemoryWarningNotification
+ object:nil];
}
}
return self;
}
+- (void)handleMemoryWarning
+{
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"memoryWarning"
+ body:nil];
+}
- (void)dealloc
{
diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h
index 6fa0e3249..829c1aeff 100644
--- a/React/Modules/RCTUIManager.h
+++ b/React/Modules/RCTUIManager.h
@@ -36,10 +36,16 @@
/**
* Update the frame of a root view. This might be in response to a screen rotation
- * or some other layout event outsde of the React-managed view hierarchy.
+ * or some other layout event outside of the React-managed view hierarchy.
*/
- (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView;
+/**
+ * Update the background color of a root view. This is usually triggered by
+ * manually setting the background color of the root view with native code.
+ */
+- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView;
+
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute
* view logic after all currently queued view updates have completed.
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index e9734c204..0edca85d9 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -338,17 +338,14 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
_viewRegistry[reactTag] = rootView;
CGRect frame = rootView.frame;
- // Register manager (TODO: should we do this, or leave it nil?)
- _viewManagerRegistry[reactTag] = _viewManagers[@"RCTView"];
-
// Register shadow view
dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
shadowView.frame = frame;
- shadowView.backgroundColor = [UIColor whiteColor];
+ shadowView.backgroundColor = rootView.backgroundColor;
+ shadowView.viewName = NSStringFromClass([rootView class]);
_shadowViewRegistry[shadowView.reactTag] = shadowView;
-
[_rootViewTags addObject:reactTag];
});
}
@@ -372,6 +369,22 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
});
}
+- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView
+{
+ RCTAssertMainThread();
+
+ NSNumber *reactTag = rootView.reactTag;
+ RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
+
+ dispatch_async(_shadowQueue, ^{
+ RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
+ RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
+ rootShadowView.backgroundColor = color;
+ [self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootShadowView];
+ [self flushUIBlocks];
+ });
+}
+
/**
* Unregisters views from registries
*/
@@ -390,6 +403,14 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
- (void)addUIBlock:(RCTViewManagerUIBlock)block
{
+ RCTAssertThread(_shadowQueue,
+ @"-[RCTUIManager addUIBlock:] should only be called from the "
+ "UIManager's _shadowQueue (it may be accessed via `bridge.uiManager.methodQueue`)");
+
+ if (!block) {
+ return;
+ }
+
if (!self.isValid) {
return;
}
@@ -795,6 +816,11 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
}
_shadowViewRegistry[reactTag] = shadowView;
+ // Shadow view is the source of truth for background color this is a little
+ // bit counter-intuitive if people try to set background color when setting up
+ // the view, but it's the only way that makes sense given our threading model
+ UIColor *backgroundColor = shadowView.backgroundColor;
+
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTAssertMainThread();
@@ -803,14 +829,15 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
// Generate default view, used for resetting default props
if (!uiManager->_defaultViews[viewName]) {
- // Note the default is setup after the props are read for the first time ever
- // for this className - this is ok because we only use the default for restoring
- // defaults, which never happens on first creation.
+ // Note the default is setup after the props are read for the first time
+ // ever for this className - this is ok because we only use the default
+ // for restoring defaults, which never happens on first creation.
uiManager->_defaultViews[viewName] = [manager view];
}
// Set properties
view.reactTag = reactTag;
+ view.backgroundColor = backgroundColor;
if ([view isKindOfClass:[UIView class]]) {
view.multipleTouchEnabled = YES;
view.userInteractionEnabled = YES; // required for touch handling
@@ -859,17 +886,33 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag)
}];
}
-- (void)batchDidComplete
-{
- // 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) {
- RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
- if (uiBlock) {
- [self addUIBlock:uiBlock];
- }
+RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point callback:(RCTResponseSenderBlock)callback) {
+ if (!reactTag) {
+ callback(@[[NSNull null]]);
+ return;
}
+ [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ UIView *view = viewRegistry[reactTag];
+ UIView *target = [view hitTest:point withEvent:nil];
+ CGRect frame = [target convertRect:target.bounds toView:view];
+
+ while (target.reactTag == nil && target.superview != nil) {
+ target = [target superview];
+ }
+
+ callback(@[
+ target.reactTag ?: [NSNull null],
+ @(frame.origin.x),
+ @(frame.origin.y),
+ @(frame.size.width),
+ @(frame.size.height),
+ ]);
+ }];
+}
+
+- (void)batchDidComplete
+{
// Set up next layout animation
if (_nextLayoutAnimation) {
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
@@ -893,6 +936,12 @@ RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag)
_nextLayoutAnimation = nil;
}
+ // Gather blocks to be executed now that layout is completed
+ for (RCTViewManager *manager in _viewManagers.allValues) {
+ RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
+ [self addUIBlock:uiBlock];
+ }
+
[self flushUIBlocks];
}
@@ -1433,7 +1482,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
}
// Add native props
- constantsNamespace[@"nativeProps"] = _viewConfigs[name];
+ constantsNamespace[@"NativeProps"] = _viewConfigs[name];
allJSConstants[name] = [constantsNamespace copy];
}];
diff --git a/React/Views/RCTNavigator.m b/React/Views/RCTNavigator.m
index 57415fbb7..381846569 100644
--- a/React/Views/RCTNavigator.m
+++ b/React/Views/RCTNavigator.m
@@ -15,6 +15,7 @@
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTNavItem.h"
+#import "RCTScrollView.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWrapperViewController.h"
diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h
index 0333a38a7..e5c1d550a 100644
--- a/React/Views/RCTScrollView.h
+++ b/React/Views/RCTScrollView.h
@@ -10,13 +10,12 @@
#import
#import "RCTAutoInsetsProtocol.h"
+#import "RCTEventDispatcher.h"
#import "RCTScrollableProtocol.h"
#import "RCTView.h"
@protocol UIScrollViewDelegate;
-@class RCTEventDispatcher;
-
@interface RCTScrollView : RCTView
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@@ -48,3 +47,16 @@
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
@end
+
+@interface RCTEventDispatcher (RCTScrollView)
+
+/**
+ * Send a scroll event.
+ * (You can send a fake scroll event by passing nil for scrollView).
+ */
+- (void)sendScrollEventWithType:(RCTScrollEventType)type
+ reactTag:(NSNumber *)reactTag
+ scrollView:(UIScrollView *)scrollView
+ userData:(NSDictionary *)userData;
+
+@end
diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m
index d4a2ad5e9..e6fdb83ca 100644
--- a/React/Views/RCTScrollView.m
+++ b/React/Views/RCTScrollView.m
@@ -21,6 +21,107 @@
CGFloat const ZINDEX_DEFAULT = 0;
CGFloat const ZINDEX_STICKY_HEADER = 50;
+@interface RCTScrollEvent : NSObject
+
+- (instancetype)initWithType:(RCTScrollEventType)type
+ reactTag:(NSNumber *)reactTag
+ scrollView:(UIScrollView *)scrollView
+ userData:(NSDictionary *)userData NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation RCTScrollEvent
+{
+ RCTScrollEventType _type;
+ UIScrollView *_scrollView;
+ NSDictionary *_userData;
+}
+
+@synthesize viewTag = _viewTag;
+
+- (instancetype)initWithType:(RCTScrollEventType)type
+ reactTag:(NSNumber *)reactTag
+ scrollView:(UIScrollView *)scrollView
+ userData:(NSDictionary *)userData
+{
+ if (self = [super init]) {
+ _type = type;
+ _viewTag = reactTag;
+ _scrollView = scrollView;
+ _userData = userData;
+ }
+ return self;
+}
+
+- (uint16_t)coalescingKey
+{
+ return 0;
+}
+
+- (NSDictionary *)body
+{
+ NSDictionary *body = @{
+ @"contentOffset": @{
+ @"x": @(_scrollView.contentOffset.x),
+ @"y": @(_scrollView.contentOffset.y)
+ },
+ @"contentInset": @{
+ @"top": @(_scrollView.contentInset.top),
+ @"left": @(_scrollView.contentInset.left),
+ @"bottom": @(_scrollView.contentInset.bottom),
+ @"right": @(_scrollView.contentInset.right)
+ },
+ @"contentSize": @{
+ @"width": @(_scrollView.contentSize.width),
+ @"height": @(_scrollView.contentSize.height)
+ },
+ @"layoutMeasurement": @{
+ @"width": @(_scrollView.frame.size.width),
+ @"height": @(_scrollView.frame.size.height)
+ },
+ @"zoomScale": @(_scrollView.zoomScale ?: 1),
+ };
+
+ if (_userData) {
+ NSMutableDictionary *mutableBody = [body mutableCopy];
+ [mutableBody addEntriesFromDictionary:_userData];
+ body = mutableBody;
+ }
+
+ return body;
+}
+
+- (NSString *)eventName
+{
+ static NSString *events[] = {
+ @"topScrollBeginDrag",
+ @"topScroll",
+ @"topScrollEndDrag",
+ @"topMomentumScrollBegin",
+ @"topMomentumScrollEnd",
+ @"topScrollAnimationEnd",
+ };
+
+ return events[_type];
+}
+
+- (BOOL)canCoalesce
+{
+ return YES;
+}
+
+- (id)coalesceWithEvent:(id)newEvent
+{
+ return newEvent;
+}
+
++ (NSString *)moduleDotMethod
+{
+ return @"RCTEventEmitter.receiveEvent";
+}
+
+@end
+
/**
* Include a custom scroll view subclass because we want to limit certain
* default UIKit behaviors such as textFields automatically scrolling
@@ -223,6 +324,24 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
}
}
+- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
+{
+ __block UIView *stickyHeader;
+
+ [_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) {
+ stickyHeader = [self contentView].reactSubviews[idx];
+ CGPoint convertedPoint = [stickyHeader convertPoint:point fromView:self];
+
+ if ([stickyHeader hitTest:convertedPoint withEvent:event]) {
+ *stop = YES;
+ } else {
+ stickyHeader = nil;
+ }
+ }];
+
+ return stickyHeader ?: [super hitTest:point withEvent:event];
+}
+
@end
@implementation RCTScrollView
@@ -424,6 +543,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
reactTag:self.reactTag
scrollView:scrollView
userData:userData];
+
// Update dispatch time
_lastScrollDispatchTime = now;
_allowNextScrollNoMatterWhat = NO;
@@ -612,3 +732,19 @@ RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
}
@end
+
+@implementation RCTEventDispatcher (RCTScrollView)
+
+- (void)sendScrollEventWithType:(RCTScrollEventType)type
+ reactTag:(NSNumber *)reactTag
+ scrollView:(UIScrollView *)scrollView
+ userData:(NSDictionary *)userData
+{
+ RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithType:type
+ reactTag:reactTag
+ scrollView:scrollView
+ userData:userData];
+ [self sendEvent:scrollEvent];
+}
+
+@end
diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h
index 83350ac46..c2e10750d 100644
--- a/React/Views/RCTShadowView.h
+++ b/React/Views/RCTShadowView.h
@@ -38,7 +38,6 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
@property (nonatomic, weak, readonly) RCTShadowView *superview;
@property (nonatomic, assign, readonly) css_node_t *cssNode;
@property (nonatomic, copy) NSString *viewName;
-@property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propagate to children
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
@property (nonatomic, assign) BOOL hasOnLayout;
diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m
index e5975dff2..6fc7f58b1 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -38,6 +38,7 @@ typedef enum {
NSMutableArray *_reactSubviews;
BOOL _recomputePadding;
BOOL _recomputeMargin;
+ BOOL _isBGColorExplicitlySet;
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
}
@@ -180,7 +181,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
// Update parent properties for children
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:parentProperties];
CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
- if (alpha < 1.0 && alpha > 0.0) {
+ if (alpha < 1.0) {
// If we see partial transparency, start propagating full transparency
properties[RCTBackgroundColorProp] = [UIColor clearColor];
} else {
@@ -515,6 +516,7 @@ RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
- (void)setBackgroundColor:(UIColor *)color
{
_backgroundColor = color;
+ _isBGColorExplicitlySet = YES;
[self dirtyPropagation];
}
diff --git a/React/Views/RCTTabBar.h b/React/Views/RCTTabBar.h
index 694af9ab3..6f491ca08 100644
--- a/React/Views/RCTTabBar.h
+++ b/React/Views/RCTTabBar.h
@@ -13,6 +13,9 @@
@interface RCTTabBar : UIView
+@property (nonatomic, strong) UIColor *tintColor;
+@property (nonatomic, strong) UIColor *barTintColor;
+
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end
diff --git a/React/Views/RCTTabBar.m b/React/Views/RCTTabBar.m
index 8a7fb4a43..b5b6240a4 100644
--- a/React/Views/RCTTabBar.m
+++ b/React/Views/RCTTabBar.m
@@ -114,6 +114,16 @@
}];
}
+- (void)setBarTintColor:(UIColor *)barTintColor
+{
+ _tabController.tabBar.barTintColor = barTintColor;
+}
+
+- (void)setTintColor:(UIColor *)tintColor
+{
+ _tabController.tabBar.tintColor = tintColor;
+}
+
#pragma mark - UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
diff --git a/React/Views/RCTTabBarManager.m b/React/Views/RCTTabBarManager.m
index c7dfe09e1..0f038c4d7 100644
--- a/React/Views/RCTTabBarManager.m
+++ b/React/Views/RCTTabBarManager.m
@@ -23,4 +23,7 @@ RCT_EXPORT_MODULE()
return [[RCTTabBar alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}
+RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
+
@end
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index 61f8240e8..d97eb5567 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -15,6 +15,8 @@
#import "RCTUtils.h"
#import "UIView+React.h"
+static const CGFloat RCTViewBorderThreshold = 0.001;
+
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
{
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
@@ -436,19 +438,22 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
if ([_backgroundColor isEqual:backgroundColor]) {
return;
}
+
_backgroundColor = backgroundColor;
[self.layer setNeedsDisplay];
}
-- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
+- (UIImage *)borderImage:(out CGRect *)contentsCenter
{
- static const CGFloat threshold = 0.001;
+ const CGFloat maxRadius = ({
+ const CGRect bounds = self.bounds;
+ MIN(bounds.size.height, bounds.size.width);
+ });
- const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width);
const CGFloat radius = MAX(0, _borderRadius);
- const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
- const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
- const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
+ const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
+ const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
+ const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
const CGFloat borderWidth = MAX(0, _borderWidth);
@@ -457,14 +462,19 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
- if (topLeftRadius < threshold &&
- topRightRadius < threshold &&
- bottomLeftRadius < threshold &&
- bottomRightRadius < threshold &&
- topWidth < threshold &&
- rightWidth < threshold &&
- bottomWidth < threshold &&
- leftWidth < threshold) {
+ const BOOL hasCornerRadii =
+ topLeftRadius > RCTViewBorderThreshold ||
+ topRightRadius > RCTViewBorderThreshold ||
+ bottomLeftRadius > RCTViewBorderThreshold ||
+ bottomRightRadius > RCTViewBorderThreshold;
+
+ const BOOL hasBorders =
+ topWidth > RCTViewBorderThreshold ||
+ rightWidth > RCTViewBorderThreshold ||
+ bottomWidth > RCTViewBorderThreshold ||
+ leftWidth > RCTViewBorderThreshold;
+
+ if (!hasCornerRadii && !hasBorders) {
return nil;
}
@@ -483,17 +493,26 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + MAX(innerTopLeftRadiusY, innerTopRightRadiusY), leftWidth + MAX(innerTopLeftRadiusX, innerBottomLeftRadiusX), bottomWidth + MAX(innerBottomLeftRadiusY, innerBottomRightRadiusY), rightWidth + + MAX(innerBottomRightRadiusX, innerTopRightRadiusX));
const CGSize size = CGSizeMake(edgeInsets.left + 1 + edgeInsets.right, edgeInsets.top + 1 + edgeInsets.bottom);
- UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
+ const CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
+ const BOOL opaque = (self.clipsToBounds || !hasCornerRadii) && alpha == 1.0;
+ UIGraphicsBeginImageContextWithOptions(size, opaque, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
- const CGRect rect = {CGPointZero, size};
- CGPathRef path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
+ const CGRect rect = {.size = size};
+
+ CGPathRef path;
+ const BOOL hasClipping = self.clipsToBounds;
+ if (hasClipping) {
+ path = CGPathCreateWithRect(rect, NULL);
+ } else {
+ path = RCTPathCreateWithRoundedRect(rect, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
+ }
if (_backgroundColor) {
CGContextSaveGState(ctx);
- CGContextAddPath(ctx, path);
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
+ CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
@@ -502,29 +521,18 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
CGContextAddPath(ctx, path);
CGPathRelease(path);
- BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
- if (hasRadius && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) {
- const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
- const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
- CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
- CGContextAddPath(ctx, insetPath);
- CGPathRelease(insetPath);
- }
+ const BOOL hasRadius = topLeftRadius > 0 || topRightRadius > 0 || bottomLeftRadius > 0 || bottomRightRadius > 0;
+ const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
+ CGPathRef insetPath = RCTPathCreateWithRoundedRect(UIEdgeInsetsInsetRect(rect, insetEdgeInsets), innerTopLeftRadiusX, innerTopLeftRadiusY, innerTopRightRadiusX, innerTopRightRadiusY, innerBottomLeftRadiusX, innerBottomLeftRadiusY, innerBottomRightRadiusX, innerBottomRightRadiusY, NULL);
+ CGContextAddPath(ctx, insetPath);
CGContextEOClip(ctx);
BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
- BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0;
- if (!hasRadius && hasEqualBorder && hasEqualColor) {
- CGContextSetStrokeColorWithColor(ctx, _borderColor);
- CGContextSetLineWidth(ctx, 2 * _borderWidth);
- CGContextClipToRect(ctx, rect);
- CGContextStrokeRect(ctx, rect);
- } else if (!hasRadius && hasEqualColor) {
+ if ((hasClipping || !hasRadius) && hasEqualColor) {
CGContextSetFillColorWithColor(ctx, _borderColor);
CGContextAddRect(ctx, rect);
- const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets);
- CGContextAddRect(ctx, insetRect);
+ CGContextAddPath(ctx, insetPath);
CGContextEOFillPath(ctx);
} else {
BOOL didSet = NO;
@@ -660,6 +668,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
}
+ CGPathRelease(insetPath);
+
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
@@ -669,8 +679,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
- (void)displayLayer:(CALayer *)layer
{
- CGRect contentsCenter = (CGRect){CGPointZero, {1, 1}};
- UIImage *image = [self generateBorderImage:&contentsCenter];
+ CGRect contentsCenter = {.size = {1, 1}};
+ UIImage *image = [self borderImage:&contentsCenter];
if (image && RCTRunningInTestEnvironment()) {
const CGSize size = self.bounds.size;
@@ -685,6 +695,46 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
layer.contentsCenter = contentsCenter;
layer.contentsScale = image.scale ?: 1.0;
layer.magnificationFilter = kCAFilterNearest;
+ layer.needsDisplayOnBoundsChange = image != nil;
+
+ [self updateClippingForLayer:layer];
+}
+
+- (void)updateClippingForLayer:(CALayer *)layer
+{
+ CALayer *mask = nil;
+ CGFloat cornerRadius = 0;
+
+ if (self.clipsToBounds) {
+ if (_borderRadius > 0 && _borderTopLeftRadius < 0 && _borderTopRightRadius < 0 && _borderBottomLeftRadius < 0 && _borderBottomRightRadius < 0) {
+ cornerRadius = _borderRadius;
+ } else {
+ const CGRect bounds = layer.bounds;
+ const CGFloat maxRadius = MIN(bounds.size.height, bounds.size.width);
+ const CGFloat radius = MAX(0, _borderRadius);
+ const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
+ const CGFloat topRightRadius = MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius);
+ const CGFloat bottomLeftRadius = MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius);
+ const CGFloat bottomRightRadius = MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius);
+
+ if (ABS(topLeftRadius - topRightRadius) < RCTViewBorderThreshold &&
+ ABS(topLeftRadius - bottomLeftRadius) < RCTViewBorderThreshold &&
+ ABS(topLeftRadius - bottomRightRadius) < RCTViewBorderThreshold) {
+ cornerRadius = topLeftRadius;
+ } else {
+ CAShapeLayer *shapeLayer = [CAShapeLayer layer];
+
+ CGPathRef path = RCTPathCreateWithRoundedRect(bounds, topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius, NULL);
+ shapeLayer.path = path;
+ CGPathRelease(path);
+
+ mask = shapeLayer;
+ }
+ }
+ }
+
+ layer.cornerRadius = cornerRadius;
+ layer.mask = mask;
}
#pragma mark Border Color
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index 8230e398d..311167761 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -228,36 +228,38 @@ RCT_VIEW_BORDER_RADIUS_PROPERTY(BottomRight)
#pragma mark - ShadowView properties
-RCT_EXPORT_SHADOW_PROPERTY(top, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(right, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(bottom, CGFloat);
+RCT_EXPORT_SHADOW_PROPERTY(backgroundColor, UIColor)
+
+RCT_EXPORT_SHADOW_PROPERTY(top, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(right, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(bottom, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(left, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(width, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(height, CGFloat);
+RCT_EXPORT_SHADOW_PROPERTY(width, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(height, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(borderBottomWidth, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, CGFloat);
+RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(borderBottomWidth, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, CGFloat)
RCT_CUSTOM_SHADOW_PROPERTY(borderWidth, CGFloat, RCTShadowView) {
[view setBorderWidth:[RCTConvert CGFloat:json]];
}
-RCT_EXPORT_SHADOW_PROPERTY(marginTop, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(marginRight, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(marginBottom, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(marginLeft, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(marginVertical, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(marginHorizontal, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(margin, CGFloat);
+RCT_EXPORT_SHADOW_PROPERTY(marginTop, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(marginRight, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(marginBottom, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(marginLeft, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(marginVertical, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(marginHorizontal, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(margin, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(paddingTop, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(paddingRight, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, CGFloat);
-RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, CGFloat);
+RCT_EXPORT_SHADOW_PROPERTY(paddingTop, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(paddingRight, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(padding, CGFloat);
RCT_EXPORT_SHADOW_PROPERTY(flex, CGFloat)
@@ -268,12 +270,6 @@ RCT_EXPORT_SHADOW_PROPERTY(alignItems, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(alignSelf, css_align_t)
RCT_REMAP_SHADOW_PROPERTY(position, positionType, css_position_type_t)
-RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, UIColor, RCTShadowView)
-{
- view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
- view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
-}
-
RCT_REMAP_SHADOW_PROPERTY(onLayout, hasOnLayout, BOOL)
@end
diff --git a/lint/linterTransform.js b/lint/linterTransform.js
deleted file mode 100644
index 669baf556..000000000
--- a/lint/linterTransform.js
+++ /dev/null
@@ -1,84 +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.
- */
-'use strict';
-
-var eslint = require('eslint');
-
-var ignoredStylisticRules = {
- 'key-spacing': false,
- 'comma-spacing': true,
- 'no-multi-spaces': true,
- 'brace-style': true,
- 'camelcase': true,
- 'consistent-this': true,
- 'eol-last': true,
- 'func-names': true,
- 'func-style': true,
- 'new-cap': true,
- 'new-parens': true,
- 'no-nested-ternary': true,
- 'no-array-constructor': true,
- 'no-lonely-if': true,
- 'no-new-object': true,
- 'no-spaced-func': true,
- 'no-space-before-semi': true,
- 'no-ternary': true,
- 'no-trailing-spaces': true,
- 'no-underscore-dangle': true,
- 'no-wrap-func': true,
- 'no-mixed-spaces-and-tabs': true,
- 'quotes': true,
- 'quote-props': true,
- 'semi': true,
- 'sort-vars': true,
- 'space-after-keywords': true,
- 'space-in-brackets': true,
- 'space-in-parens': true,
- 'space-infix-ops': true,
- 'space-return-throw-case': true,
- 'space-unary-word-ops': true,
- 'max-nested-callbacks': true,
- 'one-var': true,
- 'wrap-regex': true,
- 'curly': true,
- 'no-mixed-requires': true,
-};
-
-function setLinterTransform(transformSource) {
- var originalVerify = eslint.linter.verify;
- eslint.linter.verify = function(text, config, filename, saveState) {
- var transformedText;
- try {
- transformedText = transformSource(text, filename);
- } catch (e) {
- return [{
- severity: 2,
- line: e.lineNumber,
- message: e.message,
- source: text
- }];
- }
- var originalLines = text.split('\n');
- var transformedLines = transformedText.split('\n');
- var warnings = originalVerify.call(eslint.linter, transformedText, config, filename, saveState);
-
- // JSX and ES6 transforms usually generate pretty ugly code. Let's skip lint warnings
- // about code style for lines that have been changed by transform step.
- // Note that more important issues, like use of undefined vars, will still be reported.
- return warnings.filter(function(error) {
- var lineHasBeenTransformed = originalLines[error.line - 1] !== transformedLines[error.line - 1];
- var shouldIgnore = ignoredStylisticRules[error.ruleId] && lineHasBeenTransformed;
- return !shouldIgnore;
- });
- };
-}
-
-module.exports = {
- setLinterTransform: setLinterTransform,
-};
diff --git a/linter.js b/linter.js
deleted file mode 100644
index f243a2d2f..000000000
--- a/linter.js
+++ /dev/null
@@ -1,17 +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.
- */
-'use strict';
-
-var transformSource = require('./jestSupport/scriptPreprocess.js').transformSource;
-var linterTransform = require('./lint/linterTransform');
-
-linterTransform.setLinterTransform(transformSource);
-
-// Run the original CLI
-require('eslint/bin/eslint');
diff --git a/package.json b/package.json
index a2c7a38b4..1f47a6d34 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
},
"dependencies": {
"absolute-path": "0.0.0",
+ "babel": "5.4.3",
"bluebird": "^2.9.21",
"chalk": "^1.0.0",
"connect": "2.8.3",
@@ -58,7 +59,7 @@
"react-timer-mixin": "^0.13.1",
"react-tools": "0.13.2",
"rebound": "^0.0.12",
- "sane": "^1.1.2",
+ "sane": "git://github.com/tadeuzagallo/sane.git#a029f8b04a",
"source-map": "0.1.31",
"stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638",
"uglify-js": "~2.4.16",
@@ -69,6 +70,8 @@
},
"devDependencies": {
"jest-cli": "0.4.5",
- "eslint": "0.9.2"
+ "babel-eslint": "3.1.5",
+ "eslint": "0.21.2",
+ "eslint-plugin-react": "2.3.0"
}
}
diff --git a/packager/packager.js b/packager/packager.js
index eb90b04e0..d630d06e3 100644
--- a/packager/packager.js
+++ b/packager/packager.js
@@ -36,14 +36,18 @@ var webSocketProxy = require('./webSocketProxy.js');
var options = parseCommandLine([{
command: 'port',
default: 8081,
+ type: 'string',
}, {
command: 'root',
+ type: 'string',
description: 'add another root(s) to be used by the packager in this project',
}, {
command: 'assetRoots',
+ type: 'string',
description: 'specify the root directories of app assets'
}, {
command: 'platform',
+ type: 'string',
default: 'ios',
description: 'Specify the platform-specific blacklist (ios, android, web).'
}, {
@@ -65,13 +69,13 @@ if (options.projectRoots) {
}
if (options.root) {
- if (typeof options.root === 'string') {
- options.projectRoots.push(path.resolve(options.root));
- } else {
- options.root.forEach(function(root) {
- options.projectRoots.push(path.resolve(root));
- });
+ if (!Array.isArray(options.root)) {
+ options.root = options.root.split(',');
}
+
+ options.root.forEach(function(root) {
+ options.projectRoots.push(path.resolve(root));
+ });
}
if (options.assetRoots) {
diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js
index a7b6264b7..6be111997 100644
--- a/packager/react-packager/index.js
+++ b/packager/react-packager/index.js
@@ -18,14 +18,17 @@ exports.middleware = function(options) {
return server.processRequest.bind(server);
};
-exports.buildPackageFromUrl = function(options, reqUrl) {
- Activity.disable();
- // Don't start the filewatcher or the cache.
- if (options.nonPersistent == null) {
- options.nonPersistent = true;
- }
+exports.buildPackage = function(options, packageOptions) {
+ var server = createServer(options);
+ return server.buildPackage(packageOptions)
+ .then(function(p) {
+ server.end();
+ return p;
+ });
+};
- var server = new Server(options);
+exports.buildPackageFromUrl = function(options, reqUrl) {
+ var server = createServer(options);
return server.buildPackageFromUrl(reqUrl)
.then(function(p) {
server.end();
@@ -34,13 +37,7 @@ exports.buildPackageFromUrl = function(options, reqUrl) {
};
exports.getDependencies = function(options, main) {
- Activity.disable();
- // Don't start the filewatcher or the cache.
- if (options.nonPersistent == null) {
- options.nonPersistent = true;
- }
-
- var server = new Server(options);
+ var server = createServer(options);
return server.getDependencies(main)
.then(function(r) {
server.end();
@@ -60,3 +57,13 @@ function useGracefulFs() {
}
});
}
+
+function createServer(options) {
+ Activity.disable();
+ // Don't start the filewatcher or the cache.
+ if (options.nonPersistent == null) {
+ options.nonPersistent = true;
+ }
+
+ return new Server(options);
+}
diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js
index cd1a28e55..d90de452d 100644
--- a/packager/react-packager/src/FileWatcher/index.js
+++ b/packager/react-packager/src/FileWatcher/index.js
@@ -67,6 +67,7 @@ function createWatcher(rootConfig) {
var watcher = new Watcher(rootConfig.dir, {
glob: rootConfig.globs,
dot: false,
+ ignore: '**/node_modules/**/*',
});
return new Promise(function(resolve, reject) {
diff --git a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
index 5948916a7..22ca53e63 100644
--- a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
+++ b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
@@ -57,25 +57,34 @@ describe('Transformer', function() {
});
pit('should add file info to parse errors', function() {
+ var message = 'message';
+ var snippet = 'snippet';
+
require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'var x;\nvar answer = 1 = x;');
});
workers.mockImpl(function(data, callback) {
- var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment');
- esprimaError.description = 'Invalid left-hand side in assignment';
- esprimaError.lineNumber = 2;
- esprimaError.column = 15;
- callback(null, {error: esprimaError});
+ var babelError = new SyntaxError(message);
+ babelError.type = 'SyntaxError';
+ babelError.description = message;
+ babelError.loc = {
+ line: 2,
+ column: 15,
+ };
+ babelError.codeFrame = snippet;
+ callback(babelError);
});
return new Transformer(OPTIONS).loadFileAndTransform('foo-file.js')
.catch(function(error) {
expect(error.type).toEqual('TransformError');
- expect(error.snippet).toEqual([
- 'var answer = 1 = x;',
- ' ^',
- ].join('\n'));
+ expect(error.message).toBe('SyntaxError ' + message);
+ expect(error.lineNumber).toBe(2);
+ expect(error.column).toBe(15);
+ expect(error.filename).toBe('foo-file.js');
+ expect(error.description).toBe(message);
+ expect(error.snippet).toBe(snippet);
});
});
});
diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js
index 2dc5e20bd..513d4394e 100644
--- a/packager/react-packager/src/JSTransformer/index.js
+++ b/packager/react-packager/src/JSTransformer/index.js
@@ -99,7 +99,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
}).then(
function(res) {
if (res.error) {
- throw formatError(res.error, filePath, sourceCode);
+ console.warn(
+ 'Error property on the result value form the transformer',
+ 'module is deprecated and will be removed in future versions.',
+ 'Please pass an error object as the first argument to the callback'
+ );
+ throw formatError(res.error, filePath);
}
return new ModuleTransport({
@@ -110,6 +115,8 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
});
}
);
+ }).catch(function(err) {
+ throw formatError(err, filePath);
});
});
};
@@ -118,8 +125,8 @@ function TransformError() {}
util.inherits(TransformError, SyntaxError);
function formatError(err, filename, source) {
- if (err.lineNumber && err.column) {
- return formatEsprimaError(err, filename, source);
+ if (err.loc) {
+ return formatBabelError(err, filename, source);
} else {
return formatGenericError(err, filename, source);
}
@@ -136,26 +143,16 @@ function formatGenericError(err, filename) {
return error;
}
-function formatEsprimaError(err, filename, source) {
- var stack = err.stack.split('\n');
- stack.shift();
-
- var msg = 'TransformError: ' + err.description + ' ' + filename + ':' +
- err.lineNumber + ':' + err.column;
- var sourceLine = source.split('\n')[err.lineNumber - 1];
- var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^';
-
- stack.unshift(msg);
-
+function formatBabelError(err, filename) {
var error = new TransformError();
- error.message = msg;
error.type = 'TransformError';
- error.stack = stack.join('\n');
- error.snippet = snippet;
+ error.message = (err.type || error.type) + ' ' + err.message;
+ error.stack = err.stack;
+ error.snippet = err.codeFrame;
+ error.lineNumber = err.loc.line;
+ error.column = err.loc.column;
error.filename = filename;
- error.lineNumber = err.lineNumber;
- error.column = err.column;
- error.description = err.description;
+ error.description = err.message;
return error;
}
diff --git a/packager/react-packager/src/Packager/Package.js b/packager/react-packager/src/Packager/Package.js
index 70f57c4ab..6b5389461 100644
--- a/packager/react-packager/src/Packager/Package.js
+++ b/packager/react-packager/src/Packager/Package.js
@@ -15,6 +15,8 @@ var ModuleTransport = require('../lib/ModuleTransport');
module.exports = Package;
+var SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
+
function Package(sourceMapUrl) {
this._finalized = false;
this._modules = [];
@@ -96,12 +98,11 @@ Package.prototype.getSource = function(options) {
}
var source = this._getSource();
- source += '\n\/\/@ sourceMappingURL=';
if (options.inlineSourceMap) {
- source += this._getInlineSourceMap();
- } else {
- source += this._sourceMapUrl;
+ source += SOURCEMAPPING_URL + this._getInlineSourceMap();
+ } else if (this._sourceMapUrl) {
+ source += SOURCEMAPPING_URL + this._sourceMapUrl;
}
return source;
diff --git a/packager/react-packager/src/Packager/__tests__/Package-test.js b/packager/react-packager/src/Packager/__tests__/Package-test.js
index 1d8c3dd12..d43c65c0f 100644
--- a/packager/react-packager/src/Packager/__tests__/Package-test.js
+++ b/packager/react-packager/src/Packager/__tests__/Package-test.js
@@ -47,6 +47,26 @@ describe('Package', function() {
].join('\n'));
});
+ it('should be ok to leave out the source map url', function() {
+ var p = new Package();
+ p.addModule(new ModuleTransport({
+ code: 'transformed foo;',
+ sourceCode: 'source foo',
+ sourcePath: 'foo path',
+ }));
+ p.addModule(new ModuleTransport({
+ code: 'transformed bar;',
+ sourceCode: 'source bar',
+ sourcePath: 'bar path',
+ }));
+
+ p.finalize({});
+ expect(p.getSource()).toBe([
+ 'transformed foo;',
+ 'transformed bar;',
+ ].join('\n'));
+ });
+
it('should create a package and add run module code', function() {
ppackage.addModule(new ModuleTransport({
code: 'transformed foo;',
diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js
index 58a1aca79..e4e7b5088 100644
--- a/packager/react-packager/src/Server/__tests__/Server-test.js
+++ b/packager/react-packager/src/Server/__tests__/Server-test.js
@@ -230,7 +230,7 @@ describe('processRequest', function() {
});
});
- describe.only('/assets endpoint', function() {
+ describe('/assets endpoint', function() {
var AssetServer;
beforeEach(function() {
AssetServer = require('../../AssetServer');
@@ -257,4 +257,30 @@ describe('processRequest', function() {
});
});
+
+ describe('buildPackage(options)', function() {
+ it('Calls the packager with the correct args', function() {
+ server.buildPackage({
+ entryFile: 'foo file'
+ });
+ expect(Packager.prototype.package).toBeCalledWith(
+ 'foo file',
+ true,
+ undefined,
+ true
+ );
+ });
+ });
+
+ describe('buildPackageFromUrl(options)', function() {
+ it('Calls the packager with the correct args', function() {
+ server.buildPackageFromUrl('/path/to/foo.bundle?dev=false&runModule=false');
+ expect(Packager.prototype.package).toBeCalledWith(
+ 'path/to/foo.js',
+ false,
+ '/path/to/foo.map',
+ false
+ );
+ });
+ });
});
diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js
index d4da5ae10..0ce5c5847 100644
--- a/packager/react-packager/src/Server/index.js
+++ b/packager/react-packager/src/Server/index.js
@@ -129,7 +129,7 @@ Server.prototype._onFileChange = function(type, filepath, root) {
};
Server.prototype._rebuildPackages = function() {
- var buildPackage = this._buildPackage.bind(this);
+ var buildPackage = this.buildPackage.bind(this);
var packages = this._packages;
Object.keys(packages).forEach(function(key) {
var options = getOptionsFromUrl(key);
@@ -171,18 +171,47 @@ Server.prototype.end = function() {
]);
};
-Server.prototype._buildPackage = function(options) {
+var packageOpts = declareOpts({
+ sourceMapUrl: {
+ type: 'string',
+ required: false,
+ },
+ entryFile: {
+ type: 'string',
+ required: true,
+ },
+ dev: {
+ type: 'boolean',
+ default: true,
+ },
+ minify: {
+ type: 'boolean',
+ default: false,
+ },
+ runModule: {
+ type: 'boolean',
+ default: true,
+ },
+ inlineSourceMap: {
+ type: 'boolean',
+ default: false,
+ },
+});
+
+Server.prototype.buildPackage = function(options) {
+ var opts = packageOpts(options);
+
return this._packager.package(
- options.main,
- options.runModule,
- options.sourceMapUrl,
- options.dev
+ opts.entryFile,
+ opts.runModule,
+ opts.sourceMapUrl,
+ opts.dev
);
};
Server.prototype.buildPackageFromUrl = function(reqUrl) {
var options = getOptionsFromUrl(reqUrl);
- return this._buildPackage(options);
+ return this.buildPackage(options);
};
Server.prototype.getDependencies = function(main) {
@@ -321,7 +350,7 @@ Server.prototype.processRequest = function(req, res, next) {
var startReqEventId = Activity.startEvent('request:' + req.url);
var options = getOptionsFromUrl(req.url);
- var building = this._packages[req.url] || this._buildPackage(options);
+ var building = this._packages[req.url] || this.buildPackage(options);
this._packages[req.url] = building;
building.then(
@@ -363,7 +392,7 @@ function getOptionsFromUrl(reqUrl) {
return {
sourceMapUrl: pathname.replace(/\.bundle$/, '.map'),
- main: entryFile,
+ entryFile: entryFile,
dev: getBoolOptionFromQuery(urlObj.query, 'dev', true),
minify: getBoolOptionFromQuery(urlObj.query, 'minify'),
runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true),
diff --git a/packager/transformer.js b/packager/transformer.js
index 00beeadde..a2efcea3b 100644
--- a/packager/transformer.js
+++ b/packager/transformer.js
@@ -10,40 +10,37 @@
*/
'use strict';
-var jstransform = require('jstransform').transform;
+var babel = require('babel');
-var reactVisitors =
- require('react-tools/vendor/fbtransform/visitors').getAllVisitors();
-var staticTypeSyntax =
- require('jstransform/visitors/type-syntax').visitorList;
-var trailingCommaVisitors =
- require('jstransform/visitors/es7-trailing-comma-visitors.js').visitorList;
-
-// Note that reactVisitors now handles ES6 classes, rest parameters, arrow
-// functions, template strings, and object short notation.
-var visitorList = reactVisitors.concat(trailingCommaVisitors);
-
-function transform(srcTxt, filename) {
- var options = {
- es3: true,
- sourceType: 'nonStrictModule',
+function transform(srcTxt, filename, options) {
+ var result = babel.transform(srcTxt, {
+ retainLines: true,
+ compact: true,
+ comments: false,
filename: filename,
+ whitelist: [
+ 'es6.arrowFunctions',
+ 'es6.blockScoping',
+ 'es6.classes',
+ 'es6.destructuring',
+ 'es6.parameters.rest',
+ 'es6.properties.computed',
+ 'es6.properties.shorthand',
+ 'es6.spread',
+ 'es6.templateLiterals',
+ 'es7.trailingFunctionCommas',
+ 'es7.objectRestSpread',
+ 'flow',
+ 'react',
+ ],
+ sourceFileName: filename,
+ sourceMaps: false,
+ extra: options || {},
+ });
+
+ return {
+ code: result.code,
};
-
- // These tranforms mostly just erase type annotations and static typing
- // related statements, but they were conflicting with other tranforms.
- // Running them first solves that problem
- var staticTypeSyntaxResult = jstransform(
- staticTypeSyntax,
- srcTxt,
- options
- );
-
- return jstransform(
- visitorList,
- staticTypeSyntaxResult.code,
- options
- );
}
module.exports = function(data, callback) {
@@ -54,15 +51,8 @@ module.exports = function(data, callback) {
data.filename
);
} catch (e) {
- return callback(null, {
- error: {
- lineNumber: e.lineNumber,
- column: e.column,
- message: e.message,
- stack: e.stack,
- description: e.description
- }
- });
+ callback(e);
+ return;
}
callback(null, result);
diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh
index dcafbf2be..226dedf7f 100755
--- a/scripts/e2e-test.sh
+++ b/scripts/e2e-test.sh
@@ -65,6 +65,6 @@ cd EndToEndTest
# Make sure we installed local version of react-native
ls `basename $MARKER` > /dev/null
-flow
+flow --retries 10
xctool -scheme EndToEndTest -sdk iphonesimulator test
diff --git a/scripts/objc-test.sh b/scripts/objc-test.sh
index 6cf9a7592..e02f0fc22 100755
--- a/scripts/objc-test.sh
+++ b/scripts/objc-test.sh
@@ -25,5 +25,5 @@ trap cleanup EXIT
xctool \
-project Examples/UIExplorer/UIExplorer.xcodeproj \
- -scheme UIExplorer -sdk iphonesimulator8.1 \
+ -scheme UIExplorer -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 5,OS=8.1' \
test