Updates from Tue 5 May

This commit is contained in:
Alex Kotliarskyi 2015-05-05 14:15:51 -07:00
commit 5df5602f1a
72 changed files with 2238 additions and 883 deletions

View File

@ -22,6 +22,7 @@
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; };
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
/* End PBXBuildFile section */
@ -103,6 +104,13 @@
remoteGlobalIDString = 580C376F1AB104AF0015E709;
remoteInfo = RCTTest;
};
834C36D11AF8DA610019C93C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTSettings;
};
D85B829B1AB6D5CE003F4FE2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */;
@ -129,6 +137,7 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = UIExplorer/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = UIExplorer/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = "<group>"; };
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = ../../Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj; sourceTree = "<group>"; };
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
@ -148,6 +157,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */,
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
00D2771A1AB8C3E100DC1E48 /* libRCTWebSocketDebugger.a in Frameworks */,
58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */,
@ -200,6 +210,7 @@
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */,
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */,
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
00D2770E1AB8C2C700DC1E48 /* RCTWebSocketDebugger.xcodeproj */,
@ -293,6 +304,14 @@
name = Products;
sourceTree = "<group>";
};
834C36CE1AF8DA610019C93C /* Products */ = {
isa = PBXGroup;
children = (
834C36D21AF8DA610019C93C /* libRCTSettings.a */,
);
name = Products;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
@ -411,6 +430,10 @@
ProductGroup = 14DC67E81AB71876001358AB /* Products */;
ProjectRef = 14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */;
},
{
ProductGroup = 834C36CE1AF8DA610019C93C /* Products */;
ProjectRef = 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */;
},
{
ProductGroup = 58005BE51ABA80530062E044 /* Products */;
ProjectRef = 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */;
@ -511,6 +534,13 @@
remoteRef = 58005BED1ABA80530062E044 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
834C36D21AF8DA610019C93C /* libRCTSettings.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTSettings.a;
remoteRef = 834C36D11AF8DA610019C93C /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -632,6 +662,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -28,6 +28,7 @@ var {
} = React;
var { TestModule } = React.addons;
var Settings = require('Settings');
var createExamplePage = require('./createExamplePage');
@ -114,9 +115,14 @@ class UIExplorerList extends React.Component {
components: COMPONENTS,
apis: APIS,
}),
searchText: Settings.get('searchText'),
};
}
componentDidMount() {
this._search(this.state.searchText);
}
render() {
return (
<View style={styles.listContainer}>
@ -128,6 +134,7 @@ class UIExplorerList extends React.Component {
onChangeText={this._search.bind(this)}
placeholder="Search..."
style={styles.searchTextInput}
value={this.state.searchText}
/>
</View>
<ListView
@ -177,8 +184,10 @@ class UIExplorerList extends React.Component {
dataSource: ds.cloneWithRowsAndSections({
components: COMPONENTS.filter(filter),
apis: APIS.filter(filter),
})
}),
searchText: text,
});
Settings.set({searchText: text});
}
_onPressRow(example) {

View File

@ -17,6 +17,7 @@
'use strict';
var React = require('react-native');
var ReactIOS = require('ReactIOS');
var UIExplorerBlock = require('./UIExplorerBlock');
var UIExplorerPage = require('./UIExplorerPage');
@ -46,20 +47,28 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
getBlock: function(example, i) {
// Hack warning: This is a hack because the www UI explorer requires
// renderComponent to be called.
var originalRenderComponent = React.renderComponent;
var originalRender = React.render;
var originalRenderComponent = React.renderComponent;
var originalIOSRender = ReactIOS.render;
var originalIOSRenderComponent = ReactIOS.renderComponent;
var renderedComponent;
// TODO remove typecasts when Flow bug #6560135 is fixed
// and workaround is removed from react-native.js
(React: Object).render = (React: Object).renderComponent = function(element, container) {
(React: Object).render =
(React: Object).renderComponent =
(ReactIOS: Object).render =
(ReactIOS: Object).renderComponent =
function(element, container) {
renderedComponent = element;
};
var result = example.render(null);
if (result) {
renderedComponent = result;
}
(React: Object).renderComponent = originalRenderComponent;
(React: Object).render = originalRender;
(React: Object).renderComponent = originalRenderComponent;
(ReactIOS: Object).render = originalIOSRender;
(ReactIOS: Object).renderComponent = originalIOSRenderComponent;
return (
<UIExplorerBlock
key={i}

View File

@ -17,7 +17,6 @@
@interface RCTConvert (ART)
+ (CGPathRef)CGPath:(id)json;
+ (CTFontRef)CTFont:(id)json;
+ (CTTextAlignment)CTTextAlignment:(id)json;
+ (ARTTextFrame)ARTTextFrame:(id)json;
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json;

View File

@ -64,18 +64,6 @@
return (CGPathRef)CFAutorelease(path);
}
+ (CTFontRef)CTFont:(id)json
{
NSDictionary *dict = [self NSDictionary:json];
if (!dict) {
return nil;
}
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)dict);
CTFontRef font = CTFontCreateWithFontDescriptor(fontDescriptor, 0.0, NULL);
CFRelease(fontDescriptor);
return (CTFontRef)CFAutorelease(font);
}
RCT_ENUM_CONVERTER(CTTextAlignment, (@{
@"auto": @(kCTTextAlignmentNatural),
@"left": @(kCTTextAlignmentLeft),
@ -98,7 +86,8 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
return frame;
}
CTFontRef font = [self CTFont:dict[@"font"]];
NSDictionary *fontDict = dict[@"font"];
CTFontRef font = (__bridge CTFontRef)[self UIFont:nil withFamily:fontDict[@"fontFamily"] size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"]];
if (!font) {
return frame;
}

View File

@ -50,18 +50,11 @@ function fontAndLinesDiffer(a, b) {
return true;
}
var aTraits = a.font.NSCTFontTraitsAttribute;
var bTraits = b.font.NSCTFontTraitsAttribute;
if (
a.font.fontFamily !== b.font.fontFamily ||
a.font.fontSize !== b.font.fontSize ||
a.font.fontWeight !== b.font.fontWeight ||
a.font.fontStyle !== b.font.fontStyle ||
// TODO(6364240): remove iOS-specific attrs
a.font.NSFontFamilyAttribute !== b.font.NSFontFamilyAttribute ||
a.font.NSFontSizeAttribute !== b.font.NSFontSizeAttribute ||
aTraits.NSCTFontSymbolicTrait !== bTraits.NSCTFontSymbolicTrait
a.font.fontStyle !== b.font.fontStyle
) {
return true;
}
@ -418,14 +411,6 @@ var Shape = React.createClass({
var cachedFontObjectsFromString = {};
function extractFontTraits(isBold, isItalic) {
var italic = isItalic ? 1 : 0;
var bold = isBold ? 2 : 0;
return {
NSCTFontSymbolicTrait: italic | bold
};
}
var fontFamilyPrefix = /^[\s"']*/;
var fontFamilySuffix = /[\s"']*$/;
@ -456,10 +441,6 @@ function parseFontString(font) {
fontSize: fontSize,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal',
// TODO(6364240): remove iOS-specific attrs
NSFontFamilyAttribute: fontFamily,
NSFontSizeAttribute: fontSize,
NSCTFontTraitsAttribute: extractFontTraits(isBold, isItalic)
};
return cachedFontObjectsFromString[font];
}
@ -479,13 +460,6 @@ function extractFont(font) {
fontSize: fontSize,
fontWeight: font.fontWeight,
fontStyle: font.fontStyle,
// TODO(6364240): remove iOS-specific attrs
NSFontFamilyAttribute: fontFamily,
NSFontSizeAttribute: fontSize,
NSCTFontTraitsAttribute: extractFontTraits(
font.fontWeight === 'bold',
font.fontStyle === 'italic'
)
};
}

View File

@ -231,9 +231,11 @@ module.exports = {
var tickCount = Math.round(duration * ticksPerSecond / 1000);
var samples = [];
if (tickCount > 0) {
for (var i = 0; i <= tickCount; i++) {
samples.push(easing.call(defaults, i / tickCount));
}
}
return samples;
},

View File

@ -114,7 +114,7 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
animationTag:(NSNumber *)animationTag
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
easingSample:(NSArray *)easingSample
easingSample:(NSNumberArray *)easingSample
properties:(NSDictionary *)properties
callback:(RCTResponseSenderBlock)callback)
{

View File

@ -355,7 +355,7 @@ var ScrollResponderMixin = {
/**
* A helper function to zoom to a specific rect in the scrollview.
* @param {object} rect Should have shape {x, y, w, h}
* @param {object} rect Should have shape {x, y, width, height}
*/
scrollResponderZoomTo: function(rect: { x: number; y: number; width: number; height: number; }) {
RCTUIManagerDeprecated.zoomToRect(this.getNodeHandle(), rect);

View File

@ -211,11 +211,19 @@ var ScrollView = React.createClass({
},
scrollTo: function(destY?: number, destX?: number) {
if (Platform.OS === 'android') {
RCTUIManager.dispatchViewManagerCommand(
this.getNodeHandle(),
RCTUIManager.RCTScrollView.Commands.scrollTo,
[destX || 0, destY || 0]
);
} else {
RCTUIManager.scrollTo(
this.getNodeHandle(),
destX || 0,
destY || 0
);
}
},
scrollWithoutAnimationTo: function(destY?: number, destX?: number) {
@ -364,7 +372,7 @@ var validAttributes = {
if (Platform.OS === 'android') {
var AndroidScrollView = createReactIOSNativeComponentClass({
validAttributes: validAttributes,
uiViewClassName: 'AndroidScrollView',
uiViewClassName: 'RCTScrollView',
});
var AndroidHorizontalScrollView = createReactIOSNativeComponentClass({
validAttributes: validAttributes,

View File

@ -11,14 +11,13 @@
*/
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var POPAnimation = require('POPAnimation');
var AnimationExperimental = require('AnimationExperimental');
var NativeMethodsMixin = require('NativeMethodsMixin');
var POPAnimation = require('POPAnimation');
var React = require('React');
var Touchable = require('Touchable');
var merge = require('merge');
var copyProperties = require('copyProperties');
var onlyChild = require('onlyChild');
type State = {
@ -123,9 +122,8 @@ var TouchableBounce = React.createClass({
},
render: function() {
// Note(vjeux): use cloneWithProps once React has been upgraded
var child = onlyChild(this.props.children);
copyProperties(child.props, {
return React.cloneElement(child, {
accessible: true,
testID: this.props.testID,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
@ -135,7 +133,6 @@ var TouchableBounce = React.createClass({
onResponderRelease: this.touchableHandleResponderRelease,
onResponderTerminate: this.touchableHandleResponderTerminate
});
return child;
}
});

View File

@ -30,7 +30,7 @@ var ViewStylePropTypes = {
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
shadowColor: ReactPropTypes.string,
shadowOffset: ReactPropTypes.shape(
{h: ReactPropTypes.number, w: ReactPropTypes.number}
{width: ReactPropTypes.number, height: ReactPropTypes.number}
),
shadowOpacity: ReactPropTypes.number,
shadowRadius: ReactPropTypes.number,

View File

@ -109,15 +109,27 @@ var WebView = React.createClass({
},
goForward: function() {
RCTUIManager.webViewGoForward(this.getWebWiewHandle());
RCTUIManager.dispatchViewManagerCommand(
this.getWebWiewHandle(),
RCTUIManager.RCTWebView.Commands.goForward,
null
);
},
goBack: function() {
RCTUIManager.webViewGoBack(this.getWebWiewHandle());
RCTUIManager.dispatchViewManagerCommand(
this.getWebWiewHandle(),
RCTUIManager.RCTWebView.Commands.goBack,
null
);
},
reload: function() {
RCTUIManager.webViewReload(this.getWebWiewHandle());
RCTUIManager.dispatchViewManagerCommand(
this.getWebWiewHandle(),
RCTUIManager.RCTWebView.Commands.reload,
null
);
},
/**

View File

@ -28,6 +28,7 @@
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
var BackAndroid = require('BackAndroid');
var Dimensions = require('Dimensions');
var InteractionMixin = require('InteractionMixin');
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
var NavigatorInterceptor = require('NavigatorInterceptor');
@ -43,8 +44,9 @@ var Subscribable = require('Subscribable');
var TimerMixin = require('react-timer-mixin');
var View = require('View');
var getNavigatorContext = require('getNavigatorContext');
var clamp = require('clamp');
var flattenStyle = require('flattenStyle');
var getNavigatorContext = require('getNavigatorContext');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
var merge = require('merge');
@ -52,7 +54,17 @@ var rebound = require('rebound');
var PropTypes = React.PropTypes;
var OFF_SCREEN = {style: {opacity: 0}};
// TODO: this is not ideal because there is no guarantee that the navigator
// is full screen, hwoever we don't have a good way to measure the actual
// size of the navigator right now, so this is the next best thing.
var SCREEN_WIDTH = Dimensions.get('window').width;
var SCREEN_HEIGHT = Dimensions.get('window').height;
var SCENE_DISABLED_NATIVE_PROPS = {
style: {
left: SCREEN_WIDTH,
opacity: 0,
},
};
var __uid = 0;
function getuid() {
@ -72,7 +84,7 @@ var styles = StyleSheet.create({
bottom: 0,
top: 0,
},
currentScene: {
baseScene: {
position: 'absolute',
overflow: 'hidden',
left: 0,
@ -80,11 +92,8 @@ var styles = StyleSheet.create({
bottom: 0,
top: 0,
},
futureScene: {
overflow: 'hidden',
position: 'absolute',
left: 0,
opacity: 0,
disabledScene: {
left: SCREEN_WIDTH,
},
transitioner: {
flex: 1,
@ -231,16 +240,13 @@ var Navigator = React.createClass({
initialRouteStack: PropTypes.arrayOf(PropTypes.object),
/**
* Will emit the target route upon mounting and before each nav transition,
* overriding the handler in this.props.navigator. This overrides the onDidFocus
* handler that would be found in this.props.navigator
* Will emit the target route upon mounting and before each nav transition
*/
onWillFocus: PropTypes.func,
/**
* Will be called with the new route of each scene after the transition is
* complete or after the initial mounting. This overrides the onDidFocus
* handler that would be found in this.props.navigator
* complete or after the initial mounting
*/
onDidFocus: PropTypes.func,
@ -390,12 +396,12 @@ var Navigator = React.createClass({
return this._handleRequest.apply(null, arguments);
},
requestPop: function() {
return this.request('pop');
requestPop: function(popToBeforeRoute) {
return this.request('pop', popToBeforeRoute);
},
requestPopTo: function(route) {
return this.request('pop', route);
return this.request('popTo', route);
},
_handleRequest: function(action, arg1, arg2) {
@ -406,6 +412,8 @@ 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:
@ -414,11 +422,31 @@ var Navigator = React.createClass({
}
},
_handlePop: function(route) {
if (route) {
var hasRoute = this.state.routeStack.indexOf(route) !== -1;
_handlePop: function(popToBeforeRoute) {
if (popToBeforeRoute) {
var popToBeforeRouteIndex = this.state.routeStack.indexOf(popToBeforeRoute);
if (popToBeforeRouteIndex === -1) {
return false;
}
invariant(
popToBeforeRouteIndex <= this.state.presentedIndex,
'Cannot pop past a route that is forward in the navigator'
);
this._popN(this.state.presentedIndex - popToBeforeRouteIndex + 1);
return true;
}
if (this.state.presentedIndex === 0) {
return false;
}
this.pop();
return true;
},
_handlePopTo: function(destRoute) {
if (destRoute) {
var hasRoute = this.state.routeStack.indexOf(destRoute) !== -1;
if (hasRoute) {
this.popToRoute(route);
this.popToRoute(destRoute);
return true;
} else {
return false;
@ -552,10 +580,12 @@ var Navigator = React.createClass({
},
/**
* This happens at the end of a transition started by transitionTo
* This happens at the end of a transition started by transitionTo, and when the spring catches up to a pending gesture
*/
_completeTransition: function() {
if (this.spring.getCurrentValue() !== 1) {
// The spring has finished catching up to a gesture in progress. Remove the pending progress
// and we will be in a normal activeGesture state
if (this.state.pendingGestureProgress) {
this.state.pendingGestureProgress = null;
}
@ -580,11 +610,16 @@ var Navigator = React.createClass({
this._interactionHandle = null;
}
if (this.state.pendingGestureProgress) {
// A transition completed, but there is already another gesture happening.
// Enable the scene and set the spring to catch up with the new gesture
var gestureToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
this._enableScene(gestureToIndex);
this.spring.setEndValue(this.state.pendingGestureProgress);
return;
}
if (this.state.transitionQueue.length) {
var queuedTransition = this.state.transitionQueue.shift();
this._enableScene(queuedTransition.destIndex);
this._transitionTo(
queuedTransition.destIndex,
queuedTransition.velocity,
@ -601,7 +636,8 @@ var Navigator = React.createClass({
this._lastDidFocus = route;
if (this.props.onDidFocus) {
this.props.onDidFocus(route);
} else if (this.parentNavigator && this.parentNavigator.onDidFocus) {
}
if (this.parentNavigator && this.parentNavigator.onDidFocus) {
this.parentNavigator.onDidFocus(route);
}
},
@ -617,27 +653,51 @@ var Navigator = React.createClass({
}
if (this.props.onWillFocus) {
this.props.onWillFocus(route);
} else if (this.parentNavigator && this.parentNavigator.onWillFocus) {
}
if (this.parentNavigator && this.parentNavigator.onWillFocus) {
this.parentNavigator.onWillFocus(route);
}
},
/**
* Does not delete the scenes - merely hides them.
* Hides scenes that we are not currently on or transitioning from
*/
_hideScenes: function() {
for (var i = 0; i < this.state.routeStack.length; i++) {
// This gets called when we detach a gesture, so there will not be a
// current gesture, but there might be a transition in progress
if (i === this.state.presentedIndex || i === this.state.transitionFromIndex) {
continue;
}
var sceneRef = 'scene_' + i;
this.refs[sceneRef] &&
this.refs['scene_' + i].setNativeProps(OFF_SCREEN);
this._disableScene(i);
}
},
/**
* Push a scene off the screen, so that opacity:0 scenes will not block touches sent to the presented scenes
*/
_disableScene: function(sceneIndex) {
this.refs['scene_' + sceneIndex] &&
this.refs['scene_' + sceneIndex].setNativeProps(SCENE_DISABLED_NATIVE_PROPS);
},
/**
* Put the scene back into the state as defined by props.sceneStyle, so transitions can happen normally
*/
_enableScene: function(sceneIndex) {
// First, determine what the defined styles are for scenes in this navigator
var sceneStyle = flattenStyle(this.props.sceneStyle);
// Then restore the left value for this scene
var enabledSceneNativeProps = {
left: sceneStyle.left,
};
if (sceneIndex !== this.state.transitionFromIndex) {
// If we are not in a transition from this index, make sure opacity is 0
// to prevent the enabled scene from flashing over the presented scene
enabledSceneNativeProps.opacity = 0;
}
this.refs['scene_' + sceneIndex] &&
this.refs['scene_' + sceneIndex].setNativeProps(enabledSceneNativeProps);
},
_onAnimationStart: function() {
var fromIndex = this.state.presentedIndex;
var toIndex = this.state.presentedIndex;
@ -648,7 +708,6 @@ var Navigator = React.createClass({
}
this._setRenderSceneToHarwareTextureAndroid(fromIndex, true);
this._setRenderSceneToHarwareTextureAndroid(toIndex, true);
var navBar = this._navBar;
if (navBar && navBar.onAnimationStart) {
navBar.onAnimationStart(fromIndex, toIndex);
@ -780,6 +839,8 @@ var Navigator = React.createClass({
_attachGesture: function(gestureId) {
this.state.activeGesture = gestureId;
var gesturingToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
this._enableScene(gesturingToIndex);
},
_detachGesture: function() {
@ -810,6 +871,7 @@ var Navigator = React.createClass({
(gesture.fullDistance - gestureDetectMovement);
if (nextProgress < 0 && gesture.isDetachable) {
this._detachGesture();
this.spring.setCurrentValue(0);
}
if (this._doesGestureOverswipe(this.state.activeGesture)) {
var frictionConstant = gesture.overswipe.frictionConstant;
@ -847,13 +909,17 @@ var Navigator = React.createClass({
var travelDist = isTravelVertical ? gestureState.dy : gestureState.dx;
var oppositeAxisTravelDist =
isTravelVertical ? gestureState.dx : gestureState.dy;
var edgeHitWidth = gesture.edgeHitWidth;
if (isTravelInverted) {
currentLoc = -currentLoc;
travelDist = -travelDist;
oppositeAxisTravelDist = -oppositeAxisTravelDist;
edgeHitWidth = isTravelVertical ?
-(SCREEN_HEIGHT - edgeHitWidth) :
-(SCREEN_WIDTH - edgeHitWidth);
}
var moveStartedInRegion = gesture.edgeHitWidth == null ||
currentLoc < gesture.edgeHitWidth;
currentLoc < edgeHitWidth;
var moveTravelledFarEnough =
travelDist >= gesture.gestureDetectMovement &&
travelDist > oppositeAxisTravelDist * gesture.directionRatio;
@ -924,6 +990,7 @@ var Navigator = React.createClass({
_jumpN: function(n) {
var destIndex = this._getDestIndexWithinBounds(n);
var requestTransitionAndResetUpdatingRange = () => {
this._enableScene(destIndex);
this._transitionTo(destIndex);
this._resetUpdatingRange();
};
@ -957,12 +1024,14 @@ var Navigator = React.createClass({
var activeIDStack = this.state.idStack.slice(0, activeLength);
var activeAnimationConfigStack = this.state.sceneConfigStack.slice(0, activeLength);
var nextStack = activeStack.concat([route]);
var destIndex = nextStack.length - 1;
var nextIDStack = activeIDStack.concat([getuid()]);
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
this.props.configureScene(route),
]);
var requestTransitionAndResetUpdatingRange = () => {
this._transitionTo(nextStack.length - 1);
this._enableScene(destIndex);
this._transitionTo(destIndex);
this._resetUpdatingRange();
};
this.setState({
@ -983,6 +1052,7 @@ var Navigator = React.createClass({
'Cannot pop below zero'
);
var popIndex = this.state.presentedIndex - n;
this._enableScene(popIndex);
this._transitionTo(
popIndex,
null, // default velocity
@ -1062,7 +1132,7 @@ var Navigator = React.createClass({
indexOfRoute !== -1,
'Calling pop to route for a route that doesn\'t exist!'
);
return this.state.routeStack.length - indexOfRoute - 1;
return this.state.presentedIndex - indexOfRoute;
},
popToRoute: function(route) {
@ -1190,13 +1260,18 @@ var Navigator = React.createClass({
route,
sceneNavigatorContext
);
var initialSceneStyle = i === this.state.presentedIndex ?
styles.currentScene : styles.futureScene;
var disabledSceneStyle = null;
if (i !== this.state.presentedIndex) {
disabledSceneStyle = styles.disabledScene;
}
return (
<View
key={this.state.idStack[i]}
ref={'scene_' + i}
style={[initialSceneStyle, this.props.sceneStyle]}>
onStartShouldSetResponderCapture={() => {
return i !== this.state.presentedIndex;
}}
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
{React.cloneElement(child, {
ref: this._handleItemRef.bind(null, this.state.idStack[i]),
})}

View File

@ -78,9 +78,11 @@ var NavigatorInterceptor = React.createClass({
}
switch (action) {
case 'pop':
return this.props.onPopRequest && this.props.onPopRequest(action, arg1, arg2);
return this.props.onPopRequest && this.props.onPopRequest(arg1, arg2);
case 'popTo':
return this.props.onPopToRequest && this.props.onPopToRequest(arg1, arg2);
case 'push':
return this.props.onPushRequest && this.props.onPushRequest(action, arg1, arg2);
return this.props.onPushRequest && this.props.onPushRequest(arg1, arg2);
default:
return false;
}

View File

@ -28,9 +28,9 @@ typedef NS_ENUM(NSInteger, RCTPositionErrorCode) {
#define RCT_DEFAULT_LOCATION_ACCURACY kCLLocationAccuracyHundredMeters
typedef struct {
NSTimeInterval timeout;
NSTimeInterval maximumAge;
CLLocationAccuracy accuracy;
double timeout;
double maximumAge;
double accuracy;
} RCTLocationOptions;
@implementation RCTConvert (RCTLocationOptions)

View File

@ -29,19 +29,4 @@ var ImageStylePropTypes = {
opacity: ReactPropTypes.number,
};
// Image doesn't support padding correctly (#4841912)
var unsupportedProps = Object.keys({
padding: null,
paddingTop: null,
paddingLeft: null,
paddingRight: null,
paddingBottom: null,
paddingVertical: null,
paddingHorizontal: null,
});
for (var i = 0; i < unsupportedProps.length; i++) {
delete ImageStylePropTypes[unsupportedProps[i]];
}
module.exports = ImageStylePropTypes;

View File

@ -11,7 +11,6 @@
*/
'use strict';
var Platform = require('Platform');
var RCTExceptionsManager = require('NativeModules').ExceptionsManager;
var loadSourceMap = require('loadSourceMap');

View File

@ -79,6 +79,16 @@ function setupRedBoxErrorHandler() {
ErrorUtils.setGlobalHandler(handleErrorWithRedBox);
}
function setupRedBoxConsoleErrorHandler() {
// ExceptionsManager transitively requires Promise so we install it after
var ExceptionsManager = require('ExceptionsManager');
var Platform = require('Platform');
// TODO (#6925182): Enable console.error redbox on Android
if (__DEV__ && Platform.OS === 'ios') {
ExceptionsManager.installConsoleErrorReporter();
}
}
/**
* Sets up a set of window environment wrappers that ensure that the
* BatchedBridge is flushed after each tick. In both the case of the
@ -139,4 +149,5 @@ setupTimers();
setupAlert();
setupPromise();
setupXHR();
setupRedBoxConsoleErrorHandler();
setupGeolocation();

View File

@ -143,10 +143,7 @@
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@ -182,6 +179,7 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PREPROCESSOR_DEFINITIONS = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@ -199,6 +197,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,

View File

@ -13,8 +13,6 @@
@interface RCTPushNotificationManager : NSObject <RCTBridgeModule>
- (instancetype)initWithInitialNotification:(NSDictionary *)initialNotification NS_DESIGNATED_INITIALIZER;
+ (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification;

View File

@ -24,14 +24,8 @@ RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (instancetype)init
{
return [self initWithInitialNotification:nil];
}
- (instancetype)initWithInitialNotification:(NSDictionary *)initialNotification
{
if ((self = [super init])) {
_initialNotification = [initialNotification copy];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRemoteNotificationReceived:)
name:RCTRemoteNotificationReceived
@ -45,6 +39,12 @@ RCT_EXPORT_MODULE()
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
_initialNotification = [bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy];
}
+ (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
if ([application respondsToSelector:@selector(registerForRemoteNotifications)]) {

View File

@ -10,7 +10,6 @@
#import "RCTTestRunner.h"
#import "FBSnapshotTestController.h"
#import "RCTDefines.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTTestModule.h"
@ -18,6 +17,12 @@
#define TIMEOUT_SECONDS 240
@interface RCTBridge (RCTTestRunner)
@property (nonatomic, weak) RCTBridge *batchedBridge;
@end
@implementation RCTTestRunner
{
FBSnapshotTestController *_testController;
@ -67,7 +72,7 @@
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
RCTTestModule *testModule = rootView.bridge.batchedBridge.modules[testModuleName];
testModule.controller = _testController;
testModule.testSelector = test;
testModule.view = rootView;
@ -76,8 +81,6 @@
vc.view = [[UIView alloc] init];
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
#if RCT_DEBUG // Prevents build errors, as RCTRedBox is underfined if RCT_DEBUG=0
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
@ -86,8 +89,6 @@
error = [[RCTRedBox sharedInstance] currentErrorMessage];
}
[rootView removeFromSuperview];
[rootView.bridge invalidate];
[rootView invalidate];
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
vc.view = nil;
[[RCTRedBox sharedInstance] dismiss];
@ -98,13 +99,6 @@
} else {
RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
}
#else
expectErrorBlock(@"RCTRedBox unavailable. Set RCT_DEBUG=1 for testing.");
#endif
}
@end

View File

@ -66,7 +66,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
retries--;
}
if (!runtimeIsReady) {
RCTLogError(@"Runtime is not ready. Do you have Chrome open?");
RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not "
"paused on a breakpoint or exception and try reloading again.");
[self invalidate];
return nil;
}

View File

@ -11,8 +11,6 @@
*/
'use strict';
require('ExceptionsManager').installConsoleErrorReporter();
var React = require('React');
var StyleSheet = require('StyleSheet');
var View = require('View');

View File

@ -0,0 +1,250 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
13DBA45E1AEE749000A17CF8 /* RCTSettingsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DBA45D1AEE749000A17CF8 /* RCTSettingsManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRCTSettings.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTSettings.a; sourceTree = BUILT_PRODUCTS_DIR; };
13DBA45C1AEE749000A17CF8 /* RCTSettingsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSettingsManager.h; sourceTree = "<group>"; };
13DBA45D1AEE749000A17CF8 /* RCTSettingsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSettingsManager.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRCTSettings.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
13DBA45C1AEE749000A17CF8 /* RCTSettingsManager.h */,
13DBA45D1AEE749000A17CF8 /* RCTSettingsManager.m */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RCTSettings */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTSettings" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RCTSettings;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRCTSettings.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTSettings" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RCTSettings */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13DBA45E1AEE749000A17CF8 /* RCTSettingsManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTSettings;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTSettings;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTSettings" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTSettings" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
@interface RCTSettingsManager : NSObject <RCTBridgeModule>
- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER;
@end

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTSettingsManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
@implementation RCTSettingsManager
{
BOOL _ignoringUpdates;
NSUserDefaults *_defaults;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
- (instancetype)init
{
return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]];
}
- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults
{
if ((self = [super init])) {
_defaults = defaults;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(userDefaultsDidChange:)
name:NSUserDefaultsDidChangeNotification
object:_defaults];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)userDefaultsDidChange:(NSNotification *)note
{
if (_ignoringUpdates) {
return;
}
[_bridge.eventDispatcher sendDeviceEventWithName:@"settingsUpdated" body:[_defaults dictionaryRepresentation]];
}
- (NSDictionary *)constantsToExport
{
return @{
@"settings": [_defaults dictionaryRepresentation]
};
}
/**
* Set one or more values in the settings.
* TODO: would it be useful to have a callback for when this has completed?
*/
RCT_EXPORT_METHOD(setValues:(NSDictionary *)values)
{
_ignoringUpdates = YES;
[values enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, BOOL *stop) {
id plist = [RCTConvert NSPropertyList:json];
if (plist) {
[_defaults setObject:plist forKey:key];
} else {
[_defaults removeObjectForKey:key];
}
}];
[_defaults synchronize];
_ignoringUpdates = NO;
}
/**
* Remove some values from the settings.
*/
RCT_EXPORT_METHOD(deleteValues:(NSStringArray *)keys)
{
_ignoringUpdates = YES;
for (NSString *key in keys) {
[_defaults removeObjectForKey:key];
}
[_defaults synchronize];
_ignoringUpdates = NO;
}
@end

View File

@ -0,0 +1,34 @@
/**
* 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 Settings
* @flow
*/
'use strict';
var Settings = {
get(key: string): mixed {
console.warn('Settings is not yet supported on Android');
return null;
},
set(settings: Object) {
console.warn('Settings is not yet supported on Android');
},
watchKeys(keys: string | Array<string>, callback: Function): number {
console.warn('Settings is not yet supported on Android');
return -1;
},
clearWatch(watchId: number) {
console.warn('Settings is not yet supported on Android');
},
};
module.exports = Settings;

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Settings
* @flow
*/
'use strict';
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTSettingsManager = require('NativeModules').SettingsManager;
var invariant = require('invariant');
var subscriptions: Array<{keys: Array<string>; callback: ?Function}> = [];
var Settings = {
_settings: RCTSettingsManager.settings,
get(key: string): mixed {
return this._settings[key];
},
set(settings: Object) {
this._settings = Object.assign(this._settings, settings);
RCTSettingsManager.setValues(settings);
},
watchKeys(keys: string | Array<string>, callback: Function): number {
if (typeof keys == 'string') {
keys = [keys];
}
invariant(
Array.isArray(keys),
'keys should be a string or array of strings'
);
var sid = subscriptions.length;
subscriptions.push({keys: keys, callback: callback})
return sid;
},
clearWatch(watchId: number) {
if (watchId < subscriptions.length) {
subscriptions[watchId] = {keys: [], callback: null};
}
},
_sendObservations(body: Object) {
var _this = this;
Object.keys(body).forEach((key) => {
var newValue = body[key];
var didChange = _this._settings[key] !== newValue;
_this._settings[key] = newValue;
if (didChange) {
subscriptions.forEach((sub) => {
if (~sub.keys.indexOf(key) && sub.callback) {
sub.callback();
}
});
}
});
},
};
RCTDeviceEventEmitter.addListener(
'settingsUpdated',
Settings._sendObservations.bind(Settings)
);
module.exports = Settings;

View File

@ -37,7 +37,7 @@ var AsyncStorage = {
*/
getItem: function(
key: string,
callback: (error: ?Error, result: ?string) => void
callback?: ?(error: ?Error, result: ?string) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiGet([key], function(errors, result) {
@ -60,7 +60,7 @@ var AsyncStorage = {
setItem: function(
key: string,
value: string,
callback: ?(error: ?Error) => void
callback?: ?(error: ?Error) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
@ -78,7 +78,7 @@ var AsyncStorage = {
*/
removeItem: function(
key: string,
callback: ?(error: ?Error) => void
callback?: ?(error: ?Error) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiRemove([key], function(errors) {
@ -100,7 +100,7 @@ var AsyncStorage = {
mergeItem: function(
key: string,
value: string,
callback: ?(error: ?Error) => void
callback?: ?(error: ?Error) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
@ -119,7 +119,7 @@ var AsyncStorage = {
* don't want to call this - use removeItem or multiRemove to clear only your
* own keys instead. Returns a `Promise` object.
*/
clear: function(callback: ?(error: ?Error) => void): Promise {
clear: function(callback?: ?(error: ?Error) => void): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.clear(function(error) {
callback && callback(convertError(error));
@ -135,7 +135,7 @@ var AsyncStorage = {
/**
* Gets *all* keys known to the system, for all callers, libraries, etc. Returns a `Promise` object.
*/
getAllKeys: function(callback: (error: ?Error) => void): Promise {
getAllKeys: function(callback?: ?(error: ?Error, keys: ?Array<string>) => void): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.getAllKeys(function(error, keys) {
callback && callback(convertError(error), keys);
@ -166,7 +166,7 @@ var AsyncStorage = {
*/
multiGet: function(
keys: Array<string>,
callback: (errors: ?Array<Error>, result: ?Array<Array<string>>) => void
callback?: ?(errors: ?Array<Error>, result: ?Array<Array<string>>) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiGet(keys, function(errors, result) {
@ -189,7 +189,7 @@ var AsyncStorage = {
*/
multiSet: function(
keyValuePairs: Array<Array<string>>,
callback: ?(errors: ?Array<Error>) => void
callback?: ?(errors: ?Array<Error>) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
@ -209,7 +209,7 @@ var AsyncStorage = {
*/
multiRemove: function(
keys: Array<string>,
callback: ?(errors: ?Array<Error>) => void
callback?: ?(errors: ?Array<Error>) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiRemove(keys, function(errors) {
@ -232,7 +232,7 @@ var AsyncStorage = {
*/
multiMerge: function(
keyValuePairs: Array<Array<string>>,
callback: ?(errors: ?Array<Error>) => void
callback?: ?(errors: ?Array<Error>) => void
): Promise {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {

View File

@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return nil;
return [[UIView alloc] init]; // TODO(#1102) Remove useless views.
}
- (RCTShadowView *)shadowView

View File

@ -0,0 +1,61 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule TextUpdateTest
* @flow
*/
'use strict';
var React = require('react-native');
var TimerMixin = require('react-timer-mixin');
var {
NativeModules,
StyleSheet,
Text,
} = React;
var TestManager = NativeModules.TestManager || NativeModules.SnapshotTestManager;
var TextUpdateTest = React.createClass({
mixins: [TimerMixin],
getInitialState: function() {
return {seeMore: true};
},
componentDidMount: function() {
this.requestAnimationFrame(
() => this.setState(
{seeMore: false},
TestManager.markTestCompleted
)
);
},
render: function() {
return (
<Text
style={styles.container}
onPress={() => this.setState({seeMore: !this.state.seeMore})}>
<Text>Tap to see more (bugs)...</Text>
{this.state.seeMore && 'raw text'}
</Text>
);
},
});
var styles = StyleSheet.create({
container: {
margin: 10,
marginTop: 100,
},
});
module.exports = TextUpdateTest;

View File

@ -322,6 +322,7 @@ var MessageQueueMixin = {
processBatch: function(batch) {
var self = this;
return guardReturn(function () {
ReactUpdates.batchedUpdates(function() {
batch.forEach(function(call) {
invariant(
@ -329,16 +330,16 @@ var MessageQueueMixin = {
'All the calls should pass through the BatchedBridge module'
);
if (call.method === 'callFunctionReturnFlushedQueue') {
self.callFunction.apply(self, call.args);
self._callFunction.apply(self, call.args);
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
self.invokeCallback.apply(self, call.args);
self._invokeCallback.apply(self, call.args);
} else {
throw new Error(
'Unrecognized method called on BatchedBridge: ' + call.method);
}
});
});
return this.flushedQueue();
}, null, this._flushedQueueUnguarded, this);
},
setLoggingEnabled: function(enabled) {

View File

@ -5,14 +5,14 @@
*/
'use strict';
var dummySize = {w: undefined, h: undefined};
var dummySize = {width: undefined, height: undefined};
var sizesDiffer = function(one, two) {
one = one || dummySize;
two = two || dummySize;
return one !== two && (
one.w !== two.w ||
one.h !== two.h
one.width !== two.width ||
one.height !== two.height
);
};

View File

@ -17,12 +17,19 @@
*/
function stringifySafe(arg: any): string {
var ret;
var type = typeof arg;
if (arg === undefined) {
ret = 'undefined';
} else if (arg === null) {
ret = 'null';
} else if (typeof arg === 'string') {
} else if (type === 'string') {
ret = '"' + arg + '"';
} else if (type === 'function') {
try {
ret = arg.toString();
} catch (e) {
ret = '[function unknown]';
}
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
@ -36,7 +43,7 @@ function stringifySafe(arg: any): string {
}
}
}
return ret || '["' + typeof arg + '" failed to stringify]';
return ret || '["' + type + '" failed to stringify]';
}
module.exports = stringifySafe;

View File

@ -80,15 +80,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
__attribute__((used, section("__DATA,RCTImport"))) \
static const char *__rct_import_##module##_##method##__ = #module"."#method;
/**
* This method is used to execute a new application script. It is called
* internally whenever a JS application bundle is loaded/reloaded, but should
* probably not be used at any other time.
*/
- (void)enqueueApplicationScript:(NSString *)script
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
/**
* URL of the script that was loaded into the bridge.
*/
@ -122,14 +113,4 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
*/
- (void)reload;
/**
* Add a new observer that will be called on every screen refresh.
*/
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
/**
* Stop receiving screen refresh updates for the given observer.
*/
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
@end

View File

@ -25,6 +25,7 @@
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTSourceCode.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
@ -45,12 +46,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};
/**
* Temporarily allow to turn on and off the call batching in case someone wants
* to profile both
*/
#define BATCHED_BRIDGE 1
#ifdef __LP64__
typedef uint64_t RCTHeaderValue;
typedef struct section_64 RCTHeaderSection;
@ -61,6 +56,11 @@ typedef struct section RCTHeaderSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader
#endif
#define RCTAssertJSThread() \
RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTContextExecutor"] || \
[[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
@"This method must be called on JS thread")
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
@ -122,6 +122,7 @@ static NSArray *RCTJSMethods(void)
* RTCBridgeModule protocol to ensure they've been exported. This scanning
* functionality is disabled in release mode to improve startup performance.
*/
static NSDictionary *RCTModuleIDsByName;
static NSArray *RCTModuleNamesByID;
static NSArray *RCTModuleClassesByID;
static NSArray *RCTBridgeModuleClassesByModuleID(void)
@ -129,8 +130,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleNamesByID = [NSMutableArray array];
RCTModuleClassesByID = [NSMutableArray array];
RCTModuleIDsByName = [[NSMutableDictionary alloc] init];
RCTModuleNamesByID = [[NSMutableArray alloc] init];
RCTModuleClassesByID = [[NSMutableArray alloc] init];
Dl_info info;
dladdr(&RCTBridgeModuleClassesByModuleID, &info);
@ -162,7 +164,9 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
NSStringFromClass(cls));
// Register module
[(NSMutableArray *)RCTModuleNamesByID addObject:RCTBridgeModuleNameForClass(cls)];
NSString *moduleName = RCTBridgeModuleNameForClass(cls);
((NSMutableDictionary *)RCTModuleIDsByName)[moduleName] = @(RCTModuleNamesByID.count);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
[(NSMutableArray *)RCTModuleClassesByID addObject:cls];
}
}
@ -199,17 +203,32 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
return RCTModuleClassesByID;
}
@class RCTBatchedBridge;
@interface RCTBridge ()
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
@property (nonatomic, strong) RCTBridgeModuleProviderBlock moduleProvider;
@property (nonatomic, strong, readwrite) RCTEventDispatcher *eventDispatcher;
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context;
@end
@interface RCTBatchedBridge : RCTBridge <RCTInvalidating>
@property (nonatomic, weak) RCTBridge *parentBridge;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args
context:(NSNumber *)context;
@end
/**
@ -234,8 +253,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
dispatch_block_t _methodQueue;
}
static Class _globalExecutorClass;
static NSString *RCTStringUpToFirstArgument(NSString *methodName)
{
NSRange colonRange = [methodName rangeOfString:@":"];
@ -381,10 +398,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
case '{': {
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
NSUInteger size;
NSGetSizeAndAlignment(argumentType, &size, NULL);
void *returnValue = malloc(size);
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
void *returnValue = malloc(methodSignature.methodReturnLength);
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[_invocation setTarget:[RCTConvert class]];
[_invocation setSelector:selector];
@ -700,6 +715,7 @@ static NSDictionary *RCTLocalModulesConfig()
@"methods": [[NSMutableDictionary alloc] init]
};
localModules[moduleName] = module;
[RCTLocalModuleNames addObject:moduleName];
}
// Add method if it doesn't already exist
@ -710,72 +726,18 @@ static NSDictionary *RCTLocalModulesConfig()
@"methodID": @(methods.count),
@"type": @"local"
};
[RCTLocalMethodNames addObject:methodName];
}
// Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
[RCTLocalModuleNames addObject:moduleName];
[RCTLocalMethodNames addObject:methodName];
}
});
return localModules;
}
@interface RCTDisplayLink : NSObject <RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector NS_DESIGNATED_INITIALIZER;
@end
@interface RCTBridge (RCTDisplayLink)
- (void)_update:(CADisplayLink *)displayLink;
@end
@implementation RCTDisplayLink
{
__weak RCTBridge *_bridge;
CADisplayLink *_displayLink;
SEL _selector;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector
{
if ((self = [super init])) {
_bridge = bridge;
_selector = selector;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
return self;
}
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
if (self.isValid) {
[_displayLink invalidate];
_displayLink = nil;
}
}
- (void)_update:(CADisplayLink *)displayLink
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_bridge performSelector:_selector withObject:displayLink];
#pragma clang diagnostic pop
}
@end
@interface RCTFrameUpdate (Private)
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink;
@ -796,22 +758,6 @@ static NSDictionary *RCTLocalModulesConfig()
@end
@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
Class _executorClass;
NSURL *_bundleURL;
RCTBridgeModuleProviderBlock _moduleProvider;
RCTDisplayLink *_displayLink;
RCTDisplayLink *_vsyncDisplayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
RCTSparseArray *_scheduledCallbacks;
BOOL _loading;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
@ -819,36 +765,231 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
RCTAssertMainThread();
if ((self = [super init])) {
/**
* Pre register modules
*/
RCTLocalModulesConfig();
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
[self setUp];
}
return self;
}
- (void)dealloc
{
/**
* This runs only on the main thread, but crashes the subclass
* RCTAssertMainThread();
*/
[self invalidate];
}
- (void)bindKeys
{
RCTAssertMainThread();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
#if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
#endif
}
- (void)reload
{
/**
* AnyThread
*/
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
[self setUp];
});
}
- (void)setUp
{
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
_javaScriptExecutor = RCTCreateExecutor(executorClass);
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
RCTAssertMainThread();
_batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
}
- (BOOL)isValid
{
return _batchedBridge.isValid;
}
- (void)invalidate
{
RCTAssertMainThread();
[_batchedBridge invalidate];
_batchedBridge = nil;
}
+ (void)logMessage:(NSString *)message level:(NSString *)level
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!_latestJSExecutor.isValid) {
return;
}
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:@[level, message]
context:RCTGetExecutorID(_latestJSExecutor)
callback:^(id json, NSError *error) {}];
});
}
- (NSDictionary *)modules
{
return _batchedBridge.modules;
}
#define RCT_BRIDGE_WARN(...) \
- (void)__VA_ARGS__ \
{ \
RCTLogMustFix(@"Called method \"%@\" on top level bridge. This method should \
only be called from bridge instance in a bridge module", @(__func__)); \
}
RCT_BRIDGE_WARN(enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args)
RCT_BRIDGE_WARN(_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context)
@end
@implementation RCTBatchedBridge
{
BOOL _loading;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
RCTSparseArray *_modulesByID;
RCTSparseArray *_queuesByID;
dispatch_queue_t _methodQueue;
NSDictionary *_modulesByName;
CADisplayLink *_mainDisplayLink;
CADisplayLink *_jsDisplayLink;
NSMutableSet *_frameUpdateObservers;
NSMutableArray *_scheduledCalls;
RCTSparseArray *_scheduledCallbacks;
}
@synthesize valid = _valid;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
RCTAssertMainThread();
_parentBridge = bridge;
/**
* Set Initial State
*/
_valid = YES;
_loading = YES;
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
_queuesByID = [[RCTSparseArray alloc] init];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)];
}];
_vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)];
/**
* Initialize executor to allow enqueueing calls
*/
Class executorClass = self.executorClass ?: [RCTContextExecutor class];
_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
*/
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
[self registerModules];
/**
* Start the application script
*/
[self initJS];
}
return self;
}
- (NSURL *)bundleURL
{
return _parentBridge.bundleURL;
}
- (NSDictionary *)launchOptions
{
return _parentBridge.launchOptions;
}
/**
* Override to ensure that we won't create another nested bridge
*/
- (void)setUp {}
- (void)reload
{
[_parentBridge reload];
}
- (Class)executorClass
{
return _parentBridge.executorClass;
}
- (void)setExecutorClass:(Class)executorClass
{
RCTAssertMainThread();
_parentBridge.executorClass = executorClass;
}
- (BOOL)isLoading
{
return _loading;
}
- (BOOL)isValid
{
return _valid;
}
- (void)registerModules
{
RCTAssertMainThread();
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
for (id<RCTBridgeModule> module in _parentBridge.moduleProvider ? _parentBridge.moduleProvider() : nil) {
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
}
@ -895,7 +1036,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
// Get method queues
_queuesByID = [[RCTSparseArray alloc] init];
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
if ([module respondsToSelector:@selector(methodQueue)]) {
dispatch_queue_t queue = [module methodQueue];
@ -905,7 +1045,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
_queuesByID[moduleID] = [NSNull null];
}
}
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:module];
}
}];
}
- (void)initJS
{
RCTAssertMainThread();
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@ -918,7 +1067,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
dispatch_semaphore_signal(semaphore);
}];
_loading = YES;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
NSURL *bundleURL = _parentBridge.bundleURL;
if (_javaScriptExecutor == nil) {
/**
@ -927,15 +1078,19 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
_loading = NO;
} else if (_bundleURL) { // Allow testing without a script
} else if (bundleURL) { // Allow testing without a script
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) {
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
_loading = NO;
if (!self.isValid) {
return;
}
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
if (error != nil) {
#if RCT_DEBUG // Red box is only available in debug mode
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
@ -945,70 +1100,25 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
withDetails:[error localizedFailureReason]];
}
#endif
} else {
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) {
if (!loadError) {
/**
* Register the display link to start sending js calls after everything
* is setup
*/
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:self];
object:_parentBridge
userInfo:@{ @"bridge": self }];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}];
}
}
- (void)bindKeys
{
#if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// Workaround around the first cmd+R not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+R
// will work like a charm!
[commands registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand
action:NULL];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
// reset to normal mode
[commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTBridge *strongSelf = weakSelf;
strongSelf.executorClass = Nil;
[strongSelf reload];
}];
#if RCT_DEV // Debug executors are only available in dev mode
// reload in debug mode
[commands registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTBridge *strongSelf = weakSelf;
strongSelf.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!strongSelf.executorClass) {
strongSelf.executorClass = NSClassFromString(@"RCTWebViewExecutor");
}
if (!strongSelf.executorClass) {
RCTLogError(@"WebSocket debugger is not available. "
"Did you forget to include RCTWebSocketExecutor?");
}
[strongSelf reload];
}];
#endif
#endif
}
- (NSDictionary *)modules
@ -1019,41 +1129,33 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return _modulesByName;
}
- (void)dealloc
{
[self invalidate];
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return _javaScriptExecutor != nil;
}
- (void)invalidate
{
if (!self.isValid && _modulesByID == nil) {
if (!self.isValid) {
return;
}
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
return;
}
RCTAssertMainThread();
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Release executor
_valid = NO;
if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil;
}
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
[_displayLink invalidate];
[_vsyncDisplayLink invalidate];
_frameUpdateObservers = nil;
/**
* Main Thread deallocations
*/
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_mainDisplayLink invalidate];
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
/**
* JS Thread deallocations
*/
[_javaScriptExecutor invalidate];
[_jsDisplayLink invalidate];
// Invalidate modules
for (id target in _modulesByID.allObjects) {
@ -1063,9 +1165,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
}
// Release modules (breaks retain cycle if module has strong bridge reference)
_javaScriptExecutor = nil;
_frameUpdateObservers = nil;
_modulesByID = nil;
_queuesByID = nil;
_modulesByName = nil;
}];
}
/**
@ -1091,7 +1196,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]
arguments:@[moduleID ?: @0, methodID ?: @0, args ?: @[]]
context:RCTGetExecutorID(_javaScriptExecutor)];
}
@ -1100,6 +1205,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
- (void)_immediatelyCallTimer:(NSNumber *)timer
{
RCTAssertJSThread();
NSString *moduleDotMethod = @"RCTJSTimers.callTimers";
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
@ -1108,35 +1215,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
if (!_loading) {
#if BATCHED_BRIDGE
dispatch_block_t block = ^{
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
};
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
} else {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
}
#else
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, @[@[timer]]]
context:RCTGetExecutorID(_javaScriptExecutor)];
#endif
}
}
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTAssertJSThread();
RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError);
if (scriptLoadError) {
onComplete(scriptLoadError);
@ -1166,7 +1267,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
{
id queue = _queuesByID[moduleID];
RCTAssertJSThread();
id queue = nil;
if (moduleID) {
queue = _queuesByID[moduleID];
}
if (queue == [NSNull null]) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else {
@ -1180,17 +1287,16 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#if BATCHED_BRIDGE
__weak NSMutableArray *weakScheduledCalls = _scheduledCalls;
__weak RCTSparseArray *weakScheduledCallbacks = _scheduledCallbacks;
/**
* AnyThread
*/
__weak RCTBatchedBridge *weakSelf = self;
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
RCTProfileBeginEvent();
NSMutableArray *scheduledCalls = weakScheduledCalls;
RCTSparseArray *scheduledCallbacks = weakScheduledCallbacks;
if (!scheduledCalls || !scheduledCallbacks) {
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) {
return;
}
@ -1206,7 +1312,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
* Keep going if it any event emmiter, e.g. RCT(Device|NativeApp)?EventEmitter
*/
if ([moduleName hasSuffix:@"EventEmitter"]) {
for (NSDictionary *call in [scheduledCalls copy]) {
for (NSDictionary *call in [strongSelf->_scheduledCalls copy]) {
NSArray *callArgs = call[@"args"];
/**
* If it's the same module && method call on the bridge &&
@ -1227,9 +1333,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
*/
if (
[args[2][0] isEqual:callArgs[2][0]] &&
([moduleName isEqualToString:@"RCTEventEmitter"] ? [args[2][1] isEqual:callArgs[2][1]] : YES)
(![moduleName isEqualToString:@"RCTEventEmitter"] || [args[2][1] isEqual:callArgs[2][1]])
) {
[scheduledCalls removeObject:call];
[strongSelf->_scheduledCalls removeObject:call];
}
}
}
@ -1244,9 +1350,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
};
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
scheduledCallbacks[args[0]] = call;
strongSelf->_scheduledCallbacks[args[0]] = call;
} else {
[scheduledCalls addObject:call];
[strongSelf->_scheduledCalls addObject:call];
}
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
@ -1255,10 +1361,14 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
{
#endif
RCTAssertJSThread();
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
if (!self.isValid) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
[self _handleBuffer:json context:context];
};
@ -1274,6 +1384,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
{
RCTAssertJSThread();
if (buffer == nil || buffer == (id)kCFNull) {
return;
}
@ -1344,6 +1456,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
params:(NSArray *)params
context:(NSNumber *)context
{
RCTAssertJSThread();
if (!self.isValid) {
return NO;
}
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
@ -1367,10 +1484,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return NO;
}
__weak RCTBridge *weakSelf = self;
__weak RCTBatchedBridge *weakSelf = self;
[self dispatchBlock:^{
RCTProfileBeginEvent();
__strong RCTBridge *strongSelf = weakSelf;
RCTBatchedBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
@ -1404,18 +1521,21 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertJSThread();
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
RCTProfileBeginEvent();
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
[self dispatchBlock:^{
[observer didUpdateFrame:frameUpdate];
} forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]];
}
}
#if BATCHED_BRIDGE
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
@ -1430,67 +1550,41 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
context:RCTGetExecutorID(_javaScriptExecutor)];
}
#endif
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
{
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
}
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
{
[_frameUpdateObservers addObject:observer];
}
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
{
[_frameUpdateObservers removeObject:observer];
}
- (void)reload
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!_loading) {
// If the bridge has not loaded yet, the context will be already invalid at
// the time the javascript gets executed.
// It will crash the javascript, and even the next `load` won't render.
[self invalidate];
[self setUp];
}
});
}
+ (void)logMessage:(NSString *)message level:(NSString *)level
{
if (![_latestJSExecutor isValid]) {
return;
}
// Note: the js executor could get invalidated while we're trying to call
// this...need to watch out for that.
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:@[level, message]
context:RCTGetExecutorID(_latestJSExecutor)
callback:^(id json, NSError *error) {}];
}
- (void)startProfiling
{
if (![_bundleURL.scheme isEqualToString:@"http"]) {
RCTAssertMainThread();
if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) {
RCTLogError(@"To run the profiler you must be running from the dev server");
return;
}
[_mainDisplayLink invalidate];
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
RCTProfileInit();
}
- (void)stopProfiling
{
RCTAssertMainThread();
[_mainDisplayLink invalidate];
NSString *log = RCTProfileEnd();
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];
NSURL *URL = [NSURL URLWithString:URLString];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
URLRequest.HTTPMethod = @"POST";

View File

@ -101,6 +101,12 @@ typedef NSArray UIColorArray;
typedef NSArray CGColorArray;
+ (CGColorArray *)CGColorArray:(id)json;
/**
* Convert a JSON object to a Plist-safe equivalent by stripping null values.
*/
typedef id NSPropertyList;
+ (NSPropertyList)NSPropertyList:(id)json;
typedef BOOL css_overflow;
+ (css_overflow)css_overflow:(id)json;
+ (css_flex_direction_t)css_flex_direction_t:(id)json;

View File

@ -733,11 +733,6 @@ static BOOL RCTFontIsCondensed(UIFont *font)
isItalic = [self RCTFontStyle:style];
}
// Get font weight
if (weight) {
fontWeight = [self RCTFontWeight:weight];
}
// Gracefully handle being given a font name rather than font family, for
// example: "Helvetica Light Oblique" rather than just "Helvetica".
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
@ -756,6 +751,11 @@ static BOOL RCTFontIsCondensed(UIFont *font)
}
}
// Get font weight
if (weight) {
fontWeight = [self RCTFontWeight:weight];
}
// Get the closest font that matches the given weight for the fontFamily
UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize];
CGFloat closestWeight;
@ -805,7 +805,9 @@ NSArray *RCTConvertArrayValue(SEL type, id json)
for (NSInteger i = 0; i < idx; i++) {
[(NSMutableArray *)values addObject:json[i]];
}
if (value) {
[(NSMutableArray *)values addObject:value];
}
copy = YES;
}
}];
@ -828,6 +830,52 @@ RCT_ARRAY_CONVERTER(UIColor)
return colors;
}
static id RCTConvertPropertyListValue(id json)
{
if (!json || json == (id)kCFNull) {
return nil;
} else if ([json isKindOfClass:[NSDictionary class]]) {
__block BOOL copy = NO;
NSMutableDictionary *values = [[NSMutableDictionary alloc] initWithCapacity:[json count]];
[json enumerateKeysAndObjectsUsingBlock:^(NSString *key, id jsonValue, BOOL *stop) {
id value = RCTConvertPropertyListValue(jsonValue);
if (value) {
values[key] = value;
}
copy |= value != jsonValue;
}];
return copy ? values : json;
} else if ([json isKindOfClass:[NSArray class]]) {
__block BOOL copy = NO;
__block NSArray *values = json;
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, BOOL *stop) {
id value = RCTConvertPropertyListValue(jsonValue);
if (copy) {
if (value) {
[(NSMutableArray *)values addObject:value];
}
} else if (value != jsonValue) {
// Converted value is different, so we'll need to copy the array
values = [[NSMutableArray alloc] initWithCapacity:values.count];
for (NSInteger i = 0; i < idx; i++) {
[(NSMutableArray *)values addObject:json[i]];
}
[(NSMutableArray *)values addObject:value];
copy = YES;
}
}];
return values;
} else {
// All other JSON types are supported by property lists
return json;
}
}
+ (NSPropertyList)NSPropertyList:(id)json
{
return RCTConvertPropertyListValue(json);
}
RCT_ENUM_CONVERTER(css_overflow, (@{
@"hidden": @NO,
@"visible": @YES

View File

@ -16,10 +16,10 @@
/**
* Developer menu, useful for exposing extra functionality when debugging.
*/
@interface RCTDevMenu : NSObject <RCTBridgeModule, RCTInvalidating>
@interface RCTDevMenu : NSObject
/**
* Is the menu enabled. The menu is enabled by default in debug mode, but
* Is the menu enabled. The menu is enabled by default if RCT_DEV=1, but
* you may wish to disable it so that you can provide your own shake handler.
*/
@property (nonatomic, assign) BOOL shakeToShow;
@ -36,15 +36,16 @@
@property (nonatomic, assign) BOOL liveReloadEnabled;
/**
* The time between checks for code changes. Defaults to 1 second.
*/
@property (nonatomic, assign) NSTimeInterval liveReloadPeriod;
/**
* Manually show the menu. This will.
* Manually show the dev menu (can be called from JS).
*/
- (void)show;
/**
* Manually reload the application. Equivalent to calling [bridge reload]
* directly, but can be called from JS.
*/
- (void)reload;
@end
/**

View File

@ -10,12 +10,16 @@
#import "RCTDevMenu.h"
#import "RCTBridge.h"
#import "RCTDefines.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#import "RCTRootView.h"
#import "RCTSourceCode.h"
#import "RCTUtils.h"
#if RCT_DEV
@interface RCTBridge (Profiling)
- (void)startProfiling;
@ -24,6 +28,7 @@
@end
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
@implementation UIWindow (RCTDevMenu)
@ -36,14 +41,20 @@ static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification
@end
@interface RCTDevMenu () <UIActionSheetDelegate>
@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate>
@property (nonatomic, strong) Class executorClass;
@end
@implementation RCTDevMenu
{
NSTimer *_updateTimer;
UIActionSheet *_actionSheet;
NSUserDefaults *_defaults;
NSMutableDictionary *_settings;
NSURLSessionDataTask *_updateTask;
NSURL *_liveReloadURL;
BOOL _jsLoaded;
}
@synthesize bridge = _bridge;
@ -55,28 +66,121 @@ RCT_EXPORT_MODULE()
// We're swizzling here because it's poor form to override methods in a category,
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
// no need to call the original implementation.
#if RCT_DEV
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
#endif
}
- (instancetype)init
{
if ((self = [super init])) {
_shakeToShow = YES;
_liveReloadPeriod = 1.0; // 1 second
[[NSNotificationCenter defaultCenter] addObserver:self
_defaults = [NSUserDefaults standardUserDefaults];
[self updateSettings];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(showOnShake)
name:RCTShowDevMenuNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(updateSettings)
name:NSUserDefaultsDidChangeNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(jsLoaded)
name:RCTJavaScriptDidLoadNotification
object:nil];
#if TARGET_IPHONE_SIMULATOR
__weak RCTDevMenu *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// toggle debug menu
[commands registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf toggle];
}];
// reload in normal mode
[commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
weakSelf.executorClass = Nil;
}];
#endif
}
return self;
}
- (void)updateSettings
{
_settings = [NSMutableDictionary dictionaryWithDictionary:[_defaults objectForKey:RCTDevMenuSettingsKey]];
__weak RCTDevMenu *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
RCTDevMenu *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf.shakeToShow = [strongSelf->_settings[@"shakeToShow"] ?: @YES boolValue];
strongSelf.profilingEnabled = [strongSelf->_settings[@"profilingEnabled"] ?: @NO boolValue];
strongSelf.liveReloadEnabled = [strongSelf->_settings[@"liveReloadEnabled"] ?: @NO boolValue];
strongSelf.executorClass = NSClassFromString(strongSelf->_settings[@"executorClass"]);
});
}
- (void)jsLoaded
{
_jsLoaded = YES;
// Check if live reloading is available
_liveReloadURL = nil;
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
if (!sourceCodeModule.scriptURL) {
if (!sourceCodeModule) {
RCTLogWarn(@"RCTSourceCode module not found");
} else {
RCTLogWarn(@"RCTSourceCode module scriptURL has not been set");
}
} else if (![sourceCodeModule.scriptURL isFileURL]) {
// Live reloading is disabled when running from bundled JS file
_liveReloadURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
}
dispatch_async(dispatch_get_main_queue(), ^{
// Hit these setters again after bridge has finished loading
self.profilingEnabled = _profilingEnabled;
self.liveReloadEnabled = _liveReloadEnabled;
self.executorClass = _executorClass;
});
}
- (void)dealloc
{
[_updateTask cancel];
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)updateSetting:(NSString *)name value:(id)value
{
if (value) {
_settings[name] = value;
} else {
[_settings removeObjectForKey:name];
}
[_defaults setObject:_settings forKey:RCTDevMenuSettingsKey];
[_defaults synchronize];
}
- (void)showOnShake
{
if (_shakeToShow) {
@ -84,26 +188,54 @@ RCT_EXPORT_MODULE()
}
}
- (void)show
- (void)toggle
{
if (_actionSheet) {
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
_actionSheet = nil;
} else {
[self show];
}
}
RCT_EXPORT_METHOD(show)
{
if (_actionSheet || !_bridge) {
return;
}
NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome";
NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari";
UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, nil];
if (_liveReloadURL) {
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
[actionSheet addButtonWithTitle:liveReloadTitle];
[actionSheet addButtonWithTitle:profilingTitle];
}
[actionSheet addButtonWithTitle:@"Cancel"];
actionSheet.cancelButtonIndex = [actionSheet numberOfButtons] - 1;
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
_actionSheet = actionSheet;
}
RCT_EXPORT_METHOD(reload)
{
_jsLoaded = NO;
_liveReloadURL = nil;
[_bridge reload];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
@ -112,19 +244,17 @@ RCT_EXPORT_MODULE()
switch (buttonIndex) {
case 0: {
[_bridge reload];
[self reload];
break;
}
case 1: {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
self.executorClass = (_executorClass == cls) ? Nil : cls;
break;
}
case 2: {
Class cls = NSClassFromString(@"RCTWebViewExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
self.executorClass = (_executorClass == cls) ? Nil : cls;
break;
}
case 3: {
@ -140,84 +270,115 @@ RCT_EXPORT_MODULE()
}
}
- (void)setProfilingEnabled:(BOOL)enabled
- (void)setShakeToShow:(BOOL)shakeToShow
{
if (_profilingEnabled == enabled) {
return;
if (_shakeToShow != shakeToShow) {
_shakeToShow = shakeToShow;
[self updateSetting:@"shakeToShow" value: @(_shakeToShow)];
}
}
- (void)setProfilingEnabled:(BOOL)enabled
{
if (_profilingEnabled != enabled) {
_profilingEnabled = enabled;
if (RCTProfileIsProfiling()) {
[_bridge stopProfiling];
} else {
[self updateSetting:@"profilingEnabled" value: @(_profilingEnabled)];
}
if (_liveReloadURL && enabled != RCTProfileIsProfiling()) {
if (enabled) {
[_bridge startProfiling];
} else {
[_bridge stopProfiling];
}
}
}
- (void)setLiveReloadEnabled:(BOOL)enabled
{
if (_liveReloadEnabled == enabled) {
if (_liveReloadEnabled != enabled) {
_liveReloadEnabled = enabled;
[self updateSetting:@"liveReloadEnabled" value: @(_liveReloadEnabled)];
}
if (_liveReloadEnabled) {
[self checkForUpdates];
} else {
[_updateTask cancel];
_updateTask = nil;
}
}
- (void)setExecutorClass:(Class)executorClass
{
if (_executorClass != executorClass) {
_executorClass = executorClass;
[self updateSetting:@"executorClass" value: NSStringFromClass(executorClass)];
}
if (_bridge.executorClass != executorClass) {
// TODO (6929129): we can remove this special case test once we have better
// support for custom executors in the dev menu. But right now this is
// needed to prevent overriding a custom executor with the default if a
// custom executor has been set directly on the bridge
if (executorClass == Nil &&
(_bridge.executorClass != NSClassFromString(@"RCTWebSocketExecutor") &&
_bridge.executorClass != NSClassFromString(@"RCTWebViewExecutor"))) {
return;
}
_liveReloadEnabled = enabled;
if (_liveReloadEnabled) {
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
target:self
selector:@selector(pollForUpdates)
userInfo:nil
repeats:YES];
} else {
[_updateTimer invalidate];
_updateTimer = nil;
_bridge.executorClass = executorClass;
[self reload];
}
}
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
- (void)checkForUpdates
{
_liveReloadPeriod = liveReloadPeriod;
if (_liveReloadEnabled) {
self.liveReloadEnabled = NO;
self.liveReloadEnabled = YES;
}
if (!_jsLoaded || !_liveReloadEnabled || !_liveReloadURL) {
return;
}
- (void)pollForUpdates
{
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
if (!sourceCodeModule) {
RCTLogError(@"RCTSourceCode module not found");
self.liveReloadEnabled = NO;
if (_updateTask) {
[_updateTask cancel];
_updateTask = nil;
return;
}
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
__weak RCTDevMenu *weakSelf = self;
_updateTask = [[NSURLSession sharedSession] dataTaskWithURL:_liveReloadURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong RCTDevMenu *strongSelf = weakSelf;
if (strongSelf && strongSelf->_liveReloadEnabled) {
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
[_bridge reload];
if (!error && HTTPResponse.statusCode == 205) {
[strongSelf reload];
} else {
strongSelf->_updateTask = nil;
[strongSelf checkForUpdates];
}
}
});
}];
}
- (BOOL)isValid
{
return !_liveReloadEnabled || _updateTimer != nil;
}
- (void)invalidate
{
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
[_updateTimer invalidate];
_updateTimer = nil;
[_updateTask resume];
}
@end
#else // Unavailable when not in dev mode
@implementation RCTDevMenu
- (void)show {}
- (void)reload {}
@end
#endif
@implementation RCTBridge (RCTDevMenu)
- (RCTDevMenu *)devMenu

View File

@ -22,6 +22,6 @@
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(RCTJavaScriptCompleteBlock)onComplete;
- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(void (^)(NSError *, NSString *))onComplete;
@end

View File

@ -27,7 +27,7 @@
return self;
}
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *, NSString *))onComplete
{
// Sanitize the script URL
scriptURL = [RCTConvert NSURL:scriptURL.absoluteString];
@ -37,7 +37,7 @@
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
NSLocalizedDescriptionKey: scriptURL ? [NSString stringWithFormat:@"Script at '%@' could not be found.", scriptURL] : @"No script URL provided"
}];
onComplete(error);
onComplete(error, nil);
return;
}
@ -57,7 +57,7 @@
code:error.code
userInfo:userInfo];
}
onComplete(error);
onComplete(error, nil);
return;
}
@ -96,18 +96,10 @@
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
onComplete(error);
onComplete(error, nil);
return;
}
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
dispatch_async(dispatch_get_main_queue(), ^{
onComplete(scriptError);
});
}];
onComplete(nil, rawText);
}];
[task resume];

View File

@ -90,6 +90,17 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
{
RCTAssertMainThread();
if (input.length && flags) {
// Workaround around the first cmd not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This ensures that
// command-key modified commands will work first time.
[self registerKeyCommandWithInput:@""
modifierFlags:flags
action:nil];
}
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
modifierFlags:flags
action:@selector(RCT_handleKeyCommand:)];

View File

@ -7,10 +7,6 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDefines.h"
#if RCT_DEBUG // Red box is only available in debug mode
#import <UIKit/UIKit.h>
@interface RCTRedBox : NSObject
@ -27,5 +23,3 @@
- (void)dismiss;
@end
#endif

View File

@ -7,15 +7,14 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDefines.h"
#if RCT_DEBUG // Red box is only available in debug mode
#import "RCTRedBox.h"
#import "RCTBridge.h"
#import "RCTDefines.h"
#import "RCTUtils.h"
#if RCT_DEBUG
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, copy) NSString *lastErrorMessage;
@ -92,7 +91,7 @@
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:nil];
[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
@ -310,4 +309,19 @@
@end
#else // Disabled
@implementation RCTRedBox
+ (instancetype)sharedInstance { return nil; }
- (void)showErrorMessage:(NSString *)message {}
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details {}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack {}
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack {}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow {}
- (NSString *)currentErrorMessage { return nil; }
- (void)dismiss {}
@end
#endif

View File

@ -11,7 +11,7 @@
#import "RCTBridge.h"
@interface RCTRootView : UIView <RCTInvalidating>
@interface RCTRootView : UIView
/**
* - Designated initializer -

View File

@ -20,22 +20,34 @@
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"
@interface RCTBridge (RCTRootView)
@property (nonatomic, weak, readonly) RCTBridge *batchedBridge;
@end
@interface RCTUIManager (RCTRootView)
- (NSNumber *)allocateRootTag;
@end
@interface RCTRootContentView : RCTView <RCTInvalidating>
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge;
@end
@implementation RCTRootView
{
RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
NSString *_moduleName;
NSDictionary *_launchOptions;
UIView *_contentView;
RCTRootContentView *_contentView;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@ -52,11 +64,11 @@
_moduleName = moduleName;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bundleFinishedLoading)
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
if (!_bridge.loading) {
[self bundleFinishedLoading];
if (!_bridge.batchedBridge.isLoading) {
[self bundleFinishedLoading:_bridge.batchedBridge];
}
}
return self;
@ -73,25 +85,6 @@
return [self initWithBridge:bridge moduleName:moduleName];
}
- (BOOL)isValid
{
return _contentView.userInteractionEnabled;
}
- (void)invalidate
{
_contentView.userInteractionEnabled = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_contentView) {
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[_contentView.reactTag]];
}
}
- (UIViewController *)backingViewController
{
return _backingViewController ?: [super backingViewController];
@ -105,9 +98,19 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
- (void)bundleFinishedLoading
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
[self bundleFinishedLoading:bridge];
}
- (void)bundleFinishedLoading:(RCTBridge *)bridge
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!bridge.isValid) {
return;
}
/**
* Every root view that is created must have a unique React tag.
@ -117,19 +120,16 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
* the React tag is assigned every time we load new content.
*/
[_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds];
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_contentView addGestureRecognizer:_touchHandler];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
[self addSubview:_contentView];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": self.initialProperties ?: @{},
@"initialProps": _initialProperties ?: @{},
};
[_bridge.uiManager registerRootView:_contentView];
[_bridge enqueueJSCall:@"AppRegistry.runApplication"
[bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]];
});
}
@ -139,7 +139,6 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
[super layoutSubviews];
if (_contentView) {
_contentView.frame = self.bounds;
[_bridge.uiManager setFrame:self.bounds forRootView:_contentView];
}
}
@ -148,6 +147,12 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
return _contentView.reactTag;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_contentView removeFromSuperview];
}
@end
@implementation RCTUIManager (RCTRootView)
@ -160,3 +165,60 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
}
@end
@implementation RCTRootContentView
{
__weak RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
}
- (instancetype)initWithFrame:(CGRect)frame
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
[self setUp];
self.frame = frame;
}
return self;
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (self.reactTag && _bridge.isValid) {
[_bridge.uiManager setFrame:self.bounds forRootView:self];
}
}
- (void)setUp
{
/**
* Every root view that is created must have a unique react tag.
* Numbering of these tags goes from 1, 11, 21, 31, etc
*
* NOTE: Since the bridge persists, the RootViews might be reused, so now
* the react tag is assigned every time we load new content.
*/
self.reactTag = [_bridge.uiManager allocateRootTag];
[self addGestureRecognizer:[[RCTTouchHandler alloc] initWithBridge:_bridge]];
[_bridge.uiManager registerRootView:self];
}
- (BOOL)isValid
{
return self.userInteractionEnabled;
}
- (void)invalidate
{
self.userInteractionEnabled = NO;
}
- (void)dealloc
{
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
}
@end

View File

@ -55,8 +55,9 @@
_pendingTouches = [[NSMutableArray alloc] init];
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
// level components not build using RCT, will fail to recognize gestures.
// `cancelsTouchesInView` is needed in order to be used as a top level
// event delegated recognizer. Otherwise, lower-level components not built
// using RCT, will fail to recognize gestures.
self.cancelsTouchesInView = NO;
}
return self;
@ -165,7 +166,9 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
* from that array.
*/
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
- (void)_updateAndDispatchTouches:(NSSet *)touches
eventName:(NSString *)eventName
originatingTime:(CFTimeInterval)originatingTime
{
// Update touches
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
@ -196,15 +199,39 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
#pragma mark - Gesture Recognizer Delegate Callbacks
static BOOL RCTAllTouchesAreCancelldOrEnded(NSSet *touches)
{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan ||
touch.phase == UITouchPhaseMoved ||
touch.phase == UITouchPhaseStationary) {
return NO;
}
}
return YES;
}
static BOOL RCTAnyTouchesChanged(NSSet *touches)
{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan ||
touch.phase == UITouchPhaseMoved) {
return YES;
}
}
return NO;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
self.state = UIGestureRecognizerStateBegan;
// "start" has to record new touches before extracting the event.
// "end"/"cancel" needs to remove the touch *after* extracting the event.
[self _recordNewTouches:touches];
[self _updateAndDispatchTouches:touches eventName:@"topTouchStart" originatingTime:event.timestamp];
self.state = UIGestureRecognizerStateBegan;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
@ -213,7 +240,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
if (self.state == UIGestureRecognizerStateFailed) {
return;
}
[self _updateAndDispatchTouches:touches eventName:@"topTouchMove" originatingTime:event.timestamp];
if (self.state == UIGestureRecognizerStateBegan) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
@ -221,6 +253,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
[super touchesEnded:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp];
[self _recordRemovedTouches:touches];
if (RCTAllTouchesAreCancelldOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateEnded;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
@ -228,6 +266,12 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveTouches);
[super touchesCancelled:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp];
[self _recordRemovedTouches:touches];
if (RCTAllTouchesAreCancelldOrEnded(event.allTouches)) {
self.state = UIGestureRecognizerStateCancelled;
} else if (RCTAnyTouchesChanged(event.allTouches)) {
self.state = UIGestureRecognizerStateChanged;
}
}
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer

View File

@ -42,13 +42,17 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
{
UIWebView *_webView;
NSMutableDictionary *_objectsToInject;
NSRegularExpression *_commentsRegex;
}
@synthesize valid = _valid;
- (instancetype)initWithWebView:(UIWebView *)webView
{
if ((self = [super init])) {
_objectsToInject = [[NSMutableDictionary alloc] init];
_webView = webView ?: [[UIWebView alloc] init];
_commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]+?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL];
_webView.delegate = self;
}
return self;
@ -59,13 +63,9 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
return [self initWithWebView:nil];
}
- (BOOL)isValid
{
return _webView != nil;
}
- (void)invalidate
{
_valid = NO;
_webView.delegate = nil;
_webView = nil;
}
@ -129,10 +129,21 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
}
RCTAssert(onComplete != nil, @"");
_onApplicationScriptLoaded = onComplete;
__weak RCTWebViewExecutor *weakSelf = self;
_onApplicationScriptLoaded = ^(NSError *error){
RCTWebViewExecutor *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_valid = error == nil;
onComplete(error);
};
script = [_commentsRegex stringByReplacingMatchesInString:script
options:0
range:NSMakeRange(0, script.length)
withTemplate:@""];
script = [script stringByReplacingOccurrencesOfString:@"<script>" withString:@""];
script = [script stringByReplacingOccurrencesOfString:@"</script>" withString:@""];
if (_objectsToInject.count > 0) {
NSMutableString *scriptWithInjections = [[NSMutableString alloc] initWithString:@"/* BEGIN NATIVELY INJECTED OBJECTS */\n"];
[_objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *blockScript, BOOL *stop) {

View File

@ -1,21 +1,15 @@
/**
* @generated SignedSource<<24fa633b4dd81b7fb40c2b2b0b7c97d0>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in from github! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Go to https://github.com/facebook/css-layout !!
* !! 2) Make a pull request and get it merged !!
* !! 3) Execute ./import.sh to pull in the latest version !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Copyright (c) 2014, 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.
*
* WARNING: You should not modify this file directly. Instead:
* 1) Go to https://github.com/facebook/css-layout
* 2) Make a pull request and get it merged
* 3) Run import.sh to copy Layout.* to react-native-github
*/
#include <math.h>
@ -44,6 +38,12 @@ void init_css_node(css_node_t *node) {
node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED;
node->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED;
node->style.minDimensions[CSS_WIDTH] = CSS_UNDEFINED;
node->style.minDimensions[CSS_HEIGHT] = CSS_UNDEFINED;
node->style.maxDimensions[CSS_WIDTH] = CSS_UNDEFINED;
node->style.maxDimensions[CSS_HEIGHT] = CSS_UNDEFINED;
node->style.position[CSS_LEFT] = CSS_UNDEFINED;
node->style.position[CSS_TOP] = CSS_UNDEFINED;
node->style.position[CSS_RIGHT] = CSS_UNDEFINED;
@ -249,6 +249,10 @@ static float getPaddingAndBorder(css_node_t *node, int location) {
return getPadding(node, location) + getBorder(node, location);
}
static float getBorderAxis(css_node_t *node, css_flex_direction_t axis) {
return getBorder(node, leading[axis]) + getBorder(node, trailing[axis]);
}
static float getMarginAxis(css_node_t *node, css_flex_direction_t axis) {
return getMargin(node, leading[axis]) + getMargin(node, trailing[axis]);
}
@ -298,7 +302,8 @@ static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) {
}
static bool isDimDefined(css_node_t *node, css_flex_direction_t axis) {
return !isUndefined(node->style.dimensions[dim[axis]]);
float value = node->style.dimensions[dim[axis]];
return !isUndefined(value) && value > 0.0;
}
static bool isPosDefined(css_node_t *node, css_position_t position) {
@ -317,6 +322,30 @@ static float getPosition(css_node_t *node, css_position_t position) {
return 0;
}
static float boundAxis(css_node_t *node, css_flex_direction_t axis, float value) {
float min = CSS_UNDEFINED;
float max = CSS_UNDEFINED;
if (axis == CSS_FLEX_DIRECTION_COLUMN) {
min = node->style.minDimensions[CSS_HEIGHT];
max = node->style.maxDimensions[CSS_HEIGHT];
} else if (axis == CSS_FLEX_DIRECTION_ROW) {
min = node->style.minDimensions[CSS_WIDTH];
max = node->style.maxDimensions[CSS_WIDTH];
}
float boundValue = value;
if (!isUndefined(max) && max >= 0.0 && boundValue > max) {
boundValue = max;
}
if (!isUndefined(min) && min >= 0.0 && boundValue < min) {
boundValue = min;
}
return boundValue;
}
// When the user specifically sets a value for width or height
static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) {
// The parent already computed us a width or height. We just skip it
@ -330,7 +359,7 @@ static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) {
// The dimensions can never be smaller than the padding and border
node->layout.dimensions[dim[axis]] = fmaxf(
node->style.dimensions[dim[axis]],
boundAxis(node, axis, node->style.dimensions[dim[axis]]),
getPaddingAndBorderAxis(node, axis)
);
}
@ -347,6 +376,7 @@ static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) {
static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
/** START_GENERATED **/
css_flex_direction_t mainAxis = getFlexDirection(node);
css_flex_direction_t crossAxis = mainAxis == CSS_FLEX_DIRECTION_ROW ?
CSS_FLEX_DIRECTION_COLUMN :
@ -385,25 +415,31 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// Let's not measure the text if we already know both dimensions
if (isRowUndefined || isColumnUndefined) {
css_dim_t measure_dim = node->measure(
css_dim_t measureDim = node->measure(
node->context,
width
);
if (isRowUndefined) {
node->layout.dimensions[CSS_WIDTH] = measure_dim.dimensions[CSS_WIDTH] +
node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] +
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
}
if (isColumnUndefined) {
node->layout.dimensions[CSS_HEIGHT] = measure_dim.dimensions[CSS_HEIGHT] +
node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] +
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
}
}
return;
}
int i;
int ii;
css_node_t* child;
css_flex_direction_t axis;
// Pre-fill some dimensions straight from the parent
for (int i = 0; i < node->children_count; ++i) {
css_node_t* child = node->get_child(node->context, i);
for (i = 0; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (getAlignItem(node, child) == CSS_ALIGN_STRETCH &&
@ -411,27 +447,27 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
!isUndefined(node->layout.dimensions[dim[crossAxis]]) &&
!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
node->layout.dimensions[dim[crossAxis]] -
boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis),
getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
} else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (int ii = 0; ii < 2; ii++) {
css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
node->layout.dimensions[dim[axis]] -
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis]),
getPosition(child, trailing[axis])),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
@ -449,11 +485,12 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// We want to execute the next two loops one per line with flex-wrap
int startLine = 0;
int endLine = 0;
int nextLine = 0;
// int nextOffset = 0;
int alreadyComputedNextLayout = 0;
// We aggregate the total dimensions of the container in those two variables
float linesCrossDim = 0;
float linesMainDim = 0;
while (endLine != node->children_count) {
while (endLine < node->children_count) {
// <Loop A> Layout non flexible children and count children by type
// mainContentDim is accumulation of the dimensions and margin of all the
@ -467,8 +504,10 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
int flexibleChildrenCount = 0;
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;
for (int i = startLine; i < node->children_count; ++i) {
css_node_t* child = node->get_child(node->context, i);
float maxWidth;
for (i = startLine; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
float nextContentDim = 0;
// It only makes sense to consider a child flexible if we have a computed
@ -478,26 +517,27 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
totalFlexible += getFlex(child);
// Even if we don't know its exact size yet, we already know the padding,
// border and margin. We'll use this partial information to compute the
// remaining space.
// border and margin. We'll use this partial information, which represents
// the smallest possible size for the child, to compute the remaining
// available space.
nextContentDim = getPaddingAndBorderAxis(child, mainAxis) +
getMarginAxis(child, mainAxis);
} else {
float maxWidth = CSS_UNDEFINED;
if (mainAxis == CSS_FLEX_DIRECTION_ROW) {
// do nothing
} else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
} else {
maxWidth = CSS_UNDEFINED;
if (mainAxis != CSS_FLEX_DIRECTION_ROW) {
maxWidth = parentMaxWidth -
getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
}
}
// This is the main recursive call. We layout non flexible children.
if (nextLine == 0) {
if (alreadyComputedNextLayout == 0) {
layoutNode(child, maxWidth);
}
@ -513,11 +553,14 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// The element we are about to add would make us go to the next line
if (isFlexWrap(node) &&
!isUndefined(node->layout.dimensions[dim[mainAxis]]) &&
mainContentDim + nextContentDim > definedMainDim) {
nextLine = i + 1;
mainContentDim + nextContentDim > definedMainDim &&
// If there's only one element, then it's bigger than the content
// and needs its own line
i != startLine) {
alreadyComputedNextLayout = 1;
break;
}
nextLine = 0;
alreadyComputedNextLayout = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
}
@ -542,6 +585,26 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// remaining space
if (flexibleChildrenCount != 0) {
float flexibleMainDim = remainingMainDim / totalFlexible;
float baseMainDim;
float boundMainDim;
// Iterate over every child in the axis. If the flex share of remaining
// space doesn't meet min/max bounds, remove this child from flex
// calculations.
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (isFlex(child)) {
baseMainDim = flexibleMainDim * getFlex(child) +
getPaddingAndBorderAxis(child, mainAxis);
boundMainDim = boundAxis(child, mainAxis, baseMainDim);
if (baseMainDim != boundMainDim) {
remainingMainDim -= boundMainDim;
totalFlexible -= getFlex(child);
}
}
}
flexibleMainDim = remainingMainDim / totalFlexible;
// The non flexible children can overflow the container, in this case
// we should just assume that there is no space available.
@ -551,21 +614,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// We iterate over the full array and only apply the action on flexible
// children. This is faster than actually allocating a new array that
// contains only flexible children.
for (int i = startLine; i < endLine; ++i) {
css_node_t* child = node->get_child(node->context, i);
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (isFlex(child)) {
// At this point we know the final size of the element in the main
// dimension
child->layout.dimensions[dim[mainAxis]] = flexibleMainDim * getFlex(child) +
getPaddingAndBorderAxis(child, mainAxis);
child->layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis,
flexibleMainDim * getFlex(child) + getPaddingAndBorderAxis(child, mainAxis)
);
float maxWidth = CSS_UNDEFINED;
if (mainAxis == CSS_FLEX_DIRECTION_ROW) {
// do nothing
} else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
maxWidth = CSS_UNDEFINED;
if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
} else {
} else if (mainAxis != CSS_FLEX_DIRECTION_ROW) {
maxWidth = parentMaxWidth -
getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
@ -580,9 +642,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// space available
} else {
css_justify_t justifyContent = getJustifyContent(node);
if (justifyContent == CSS_JUSTIFY_FLEX_START) {
// Do nothing
} else if (justifyContent == CSS_JUSTIFY_CENTER) {
if (justifyContent == CSS_JUSTIFY_CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSS_JUSTIFY_FLEX_END) {
leadingMainDim = remainingMainDim;
@ -612,8 +672,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
float mainDim = leadingMainDim +
getPaddingAndBorder(node, leading[mainAxis]);
for (int i = startLine; i < endLine; ++i) {
css_node_t* child = node->get_child(node->context, i);
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
@ -638,7 +698,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis));
crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));
}
}
@ -648,15 +708,15 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
crossDim + getPaddingAndBorderAxis(node, crossAxis),
boundAxis(node, crossAxis, crossDim + getPaddingAndBorderAxis(node, crossAxis)),
getPaddingAndBorderAxis(node, crossAxis)
);
}
// <Loop D> Position elements in the cross axis
for (int i = startLine; i < endLine; ++i) {
css_node_t* child = node->get_child(node->context, i);
for (i = startLine; i < endLine; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[crossAxis])) {
@ -674,21 +734,19 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// alignSelf (child) in order to determine the position in the cross axis
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
css_align_t alignItem = getAlignItem(node, child);
if (alignItem == CSS_ALIGN_FLEX_START) {
// Do nothing
} else if (alignItem == CSS_ALIGN_STRETCH) {
if (alignItem == CSS_ALIGN_STRETCH) {
// You can only stretch if the dimension has not already been set
// previously.
if (!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
containerCrossAxis -
boundAxis(child, crossAxis, containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis),
getMarginAxis(child, crossAxis)),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
}
} else {
} else if (alignItem != CSS_ALIGN_FLEX_START) {
// The remaining space between the parent dimensions+padding and child
// dimensions+margin.
float remainingCrossDim = containerCrossAxis -
@ -719,7 +777,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
node->layout.dimensions[dim[mainAxis]] = fmaxf(
// We're missing the last padding at this point to get the final
// dimension
linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]),
boundAxis(node, mainAxis, linesMainDim + getPaddingAndBorder(node, trailing[mainAxis])),
// We can never assign a width smaller than the padding and borders
getPaddingAndBorderAxis(node, mainAxis)
);
@ -730,37 +788,38 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
linesCrossDim + getPaddingAndBorderAxis(node, crossAxis),
boundAxis(node, crossAxis, linesCrossDim + getPaddingAndBorderAxis(node, crossAxis)),
getPaddingAndBorderAxis(node, crossAxis)
);
}
// <Loop E> Calculate dimensions for absolutely positioned elements
for (int i = 0; i < node->children_count; ++i) {
css_node_t* child = node->get_child(node->context, i);
for (i = 0; i < node->children_count; ++i) {
child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (int ii = 0; ii < 2; ii++) {
css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
boundAxis(child, axis, node->layout.dimensions[dim[axis]] -
getBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis]),
getPosition(child, trailing[axis])
),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
for (int ii = 0; ii < 2; ii++) {
css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
for (ii = 0; ii < 2; ii++) {
axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (isPosDefined(child, trailing[axis]) &&
!isPosDefined(child, leading[axis])) {
child->layout.position[leading[axis]] =

View File

@ -1,21 +1,15 @@
/**
* @generated SignedSource<<58298c7a8815a8675e970b0347dedfed>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in from github! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Go to https://github.com/facebook/css-layout !!
* !! 2) Make a pull request and get it merged !!
* !! 3) Execute ./import.sh to pull in the latest version !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Copyright (c) 2014, 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.
*
* WARNING: You should not modify this file directly. Instead:
* 1) Go to https://github.com/facebook/css-layout
* 2) Make a pull request and get it merged
* 3) Run import.sh to copy Layout.* to react-native-github
*/
#ifndef __LAYOUT_H
@ -113,6 +107,8 @@ typedef struct {
float padding[4];
float border[4];
float dimensions[2];
float minDimensions[2];
float maxDimensions[2];
} css_style_t;
typedef struct css_node {

15
React/Layout/import.sh Executable file
View File

@ -0,0 +1,15 @@
LAYOUT_C=`curl https://raw.githubusercontent.com/facebook/css-layout/master/src/Layout.c`
LAYOUT_H=`curl https://raw.githubusercontent.com/facebook/css-layout/master/src/Layout.h`
REPLACE_STRING="*
* WARNING: You should not modify this file directly. Instead:
* 1) Go to https://github.com/facebook/css-layout
* 2) Make a pull request and get it merged
* 3) Run import.sh to copy Layout.* to react-native-github
*/"
LAYOUT_C=${LAYOUT_C/\*\//$REPLACE_STRING}
LAYOUT_H=${LAYOUT_H/\*\//$REPLACE_STRING}
echo "$LAYOUT_C" > Layout.c
echo "$LAYOUT_H" > Layout.h

View File

@ -61,12 +61,8 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
} else {
// Filter out numbers so the same base errors are mapped to the same categories independent of incorrect values.
NSString *pattern = @"[+-]?\\d+[,.]?\\d*";
NSString *sanitizedMessage = [message stringByReplacingOccurrencesOfString:pattern withString:@"<num>" options:NSRegularExpressionSearch range:(NSRange){0, message.length}];
if (sanitizedMessage.length > maxMessageLength) {
sanitizedMessage = [[sanitizedMessage substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
if (message.length > maxMessageLength) {
message = [[message substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
}
NSMutableString *prettyStack = [NSMutableString stringWithString:@"\n"];
@ -74,7 +70,7 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
[prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]];
}
NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage];
NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:message];
[NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack];
}

View File

@ -70,6 +70,7 @@
}
@synthesize bridge = _bridge;
@synthesize paused = _paused;
RCT_EXPORT_MODULE()
@ -78,7 +79,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (instancetype)init
{
if ((self = [super init])) {
_paused = YES;
_timers = [[RCTSparseArray alloc] init];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
@ -126,7 +127,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
- (void)stopTimers
{
[_bridge removeFrameUpdateObserver:self];
_paused = YES;
}
- (void)startTimers
@ -135,7 +136,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
return;
}
[_bridge addFrameUpdateObserver:self];
_paused = NO;
}
- (void)didUpdateFrame:(RCTFrameUpdate *)update

View File

@ -267,11 +267,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
return self;
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
- (BOOL)isValid
{
return _viewRegistry != nil;
@ -279,8 +274,11 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
- (void)invalidate
{
RCTAssertMainThread();
/**
* Called on the JS Thread since all modules are invalidated on the JS thread
*/
dispatch_async(dispatch_get_main_queue(), ^{
for (NSNumber *rootViewTag in _rootViewTags) {
((UIView *)_viewRegistry[rootViewTag]).userInteractionEnabled = NO;
}
@ -293,6 +291,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass, NSString *viewNa
[_pendingUIBlocksLock lock];
_pendingUIBlocks = nil;
[_pendingUIBlocksLock unlock];
});
}
- (void)setBridge:(RCTBridge *)bridge

View File

@ -583,7 +583,9 @@
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
"RCT_DEBUG=1",
"RCT_DEV=1",
"RCT_NSASSERT=1",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -621,6 +623,7 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PREPROCESSOR_DEFINITIONS = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@ -638,10 +641,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -658,6 +658,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",

View File

@ -264,9 +264,13 @@ NSInteger kNeverProgressed = -10000;
NSInteger _numberOfViewControllerMovesToIgnore;
}
@synthesize paused = _paused;
- (id)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithFrame:CGRectZero])) {
_paused = YES;
_bridge = bridge;
_mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
@ -341,14 +345,14 @@ NSInteger kNeverProgressed = -10000;
_dummyView.frame = (CGRect){{destination}};
_currentlyTransitioningFrom = indexOfFrom;
_currentlyTransitioningTo = indexOfTo;
[_bridge addFrameUpdateObserver:self];
_paused = NO;
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[weakSelf freeLock];
_currentlyTransitioningFrom = 0;
_currentlyTransitioningTo = 0;
_dummyView.frame = CGRectZero;
[_bridge removeFrameUpdateObserver:self];
_paused = YES;
// Reset the parallel position tracker
}];
}
@ -457,6 +461,10 @@ NSInteger kNeverProgressed = -10000;
// --- previously caught up -------- ------- still caught up ----------
viewControllerCount == previousReactCount && currentReactCount == previousReactCount;
BOOL jsGettingtooSlow =
// --- previously not caught up -------- ------- no longer caught up ----------
viewControllerCount < previousReactCount && currentReactCount < previousReactCount;
BOOL reactPushOne = jsGettingAhead && currentReactCount == previousReactCount + 1;
BOOL reactPopN = jsGettingAhead && currentReactCount < previousReactCount;
@ -467,7 +475,8 @@ NSInteger kNeverProgressed = -10000;
if (!(jsGettingAhead ||
jsCatchingUp ||
jsMakingNoProgressButNeedsToCatchUp ||
jsMakingNoProgressAndDoesntNeedTo)) {
jsMakingNoProgressAndDoesntNeedTo ||
jsGettingtooSlow)) {
RCTLogError(@"JS has only made partial progress to catch up to UIKit");
}
if (currentReactCount > _currentViews.count) {

View File

@ -18,24 +18,6 @@
#import "RCTWrapperViewController.h"
#import "UIView+React.h"
@interface RKCustomTabBarController : UITabBarController <RCTViewControllerProtocol>
@end
@implementation RKCustomTabBarController
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
@synthesize currentBottomLayoutGuide = _currentBottomLayoutGuide;
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
_currentTopLayoutGuide = self.topLayoutGuide;
_currentBottomLayoutGuide = self.bottomLayoutGuide;
}
@end
@interface RCTTabBar() <UITabBarControllerDelegate>
@end
@ -53,7 +35,7 @@
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_tabViews = [[NSMutableArray alloc] init];
_tabController = [[RKCustomTabBarController alloc] init];
_tabController = [[UITabBarController alloc] init];
_tabController.delegate = self;
[self addSubview:_tabController.view];
}

View File

@ -57,7 +57,7 @@
"react-timer-mixin": "^0.13.1",
"react-tools": "0.13.2",
"rebound": "^0.0.12",
"sane": "1.0.3",
"sane": "^1.1.2",
"source-map": "0.1.31",
"stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638",
"uglify-js": "~2.4.16",

View File

@ -35,25 +35,34 @@
function getNativeLogFunction(level) {
return function() {
var str = Array.prototype.map.call(arguments, function(arg) {
if (arg == null) {
return arg === null ? 'null' : 'undefined';
} else if (typeof arg === 'string') {
return '"' + arg + '"';
var ret;
var type = typeof arg;
if (arg === null) {
ret = 'null';
} else if (arg === undefined) {
ret = 'undefined';
} else if (type === 'string') {
ret = '"' + arg + '"';
} else if (type === 'function') {
try {
ret = arg.toString();
} catch (e) {
ret = '[function unknown]';
}
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
try {
return JSON.stringify(arg);
ret = JSON.stringify(arg);
} catch (e) {
if (typeof arg.toString === 'function') {
try {
return arg.toString();
} catch (E) {
return 'unknown';
}
ret = arg.toString();
} catch (E) {}
}
}
}
return ret || '["' + type + '" failed to stringify]';
}).join(', ');
global.nativeLoggingHook(str, level);
};

View File

@ -11,6 +11,7 @@
jest
.dontMock('worker-farm')
.dontMock('os')
.dontMock('../../lib/ModuleTransport')
.dontMock('../index');
var OPTIONS = {
@ -37,7 +38,7 @@ describe('Transformer', function() {
pit('should loadFileAndTransform', function() {
workers.mockImpl(function(data, callback) {
callback(null, { code: 'transformed' });
callback(null, { code: 'transformed', map: 'sourceMap' });
});
require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'content');
@ -47,6 +48,7 @@ describe('Transformer', function() {
.then(function(data) {
expect(data).toEqual({
code: 'transformed',
map: 'sourceMap',
sourcePath: 'file',
sourceCode: 'content'
});

View File

@ -14,6 +14,7 @@ var Cache = require('./Cache');
var workerFarm = require('worker-farm');
var declareOpts = require('../lib/declareOpts');
var util = require('util');
var ModuleTransport = require('../lib/ModuleTransport');
var readFile = Promise.promisify(fs.readFile);
@ -100,11 +101,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
throw formatError(res.error, filePath, sourceCode);
}
return {
return new ModuleTransport({
code: res.code,
map: res.map,
sourcePath: filePath,
sourceCode: sourceCode
};
sourceCode: sourceCode,
});
}
);
});

View File

@ -11,6 +11,7 @@
var _ = require('underscore');
var base64VLQ = require('./base64-vlq');
var UglifyJS = require('uglify-js');
var ModuleTransport = require('../lib/ModuleTransport');
module.exports = Package;
@ -19,22 +20,25 @@ function Package(sourceMapUrl) {
this._modules = [];
this._assets = [];
this._sourceMapUrl = sourceMapUrl;
this._shouldCombineSourceMaps = false;
}
Package.prototype.setMainModuleId = function(moduleId) {
this._mainModuleId = moduleId;
};
Package.prototype.addModule = function(
transformedCode,
sourceCode,
sourcePath
) {
this._modules.push({
transformedCode: transformedCode,
sourceCode: sourceCode,
sourcePath: sourcePath
});
Package.prototype.addModule = function(module) {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expeceted a ModuleTransport object');
}
// If we get a map from the transformer we'll switch to a mode
// were we're combining the source maps as opposed to
if (!this._shouldCombineSourceMaps && module.map != null) {
this._shouldCombineSourceMaps = true;
}
this._modules.push(module);
};
Package.prototype.addAsset = function(asset) {
@ -45,11 +49,12 @@ Package.prototype.finalize = function(options) {
options = options || {};
if (options.runMainModule) {
var runCode = ';require("' + this._mainModuleId + '");';
this.addModule(
runCode,
runCode,
'RunMainModule.js'
);
this.addModule(new ModuleTransport({
code: runCode,
virtual: true,
sourceCode: runCode,
sourcePath: 'RunMainModule.js'
}));
}
Object.freeze(this._modules);
@ -67,7 +72,7 @@ Package.prototype._assertFinalized = function() {
Package.prototype._getSource = function() {
if (this._source == null) {
this._source = _.pluck(this._modules, 'transformedCode').join('\n');
this._source = _.pluck(this._modules, 'code').join('\n');
}
return this._source;
};
@ -136,10 +141,50 @@ Package.prototype.getMinifiedSourceAndMap = function() {
}
};
/**
* I found a neat trick in the sourcemap spec that makes it easy
* to concat sourcemaps. The `sections` field allows us to combine
* the sourcemap easily by adding an offset. Tested on chrome.
* Seems like it's not yet in Firefox but that should be fine for
* now.
*/
Package.prototype._getCombinedSourceMaps = function(options) {
var result = {
version: 3,
file: 'bundle.js',
sections: [],
};
var line = 0;
this._modules.forEach(function(module) {
var map = module.map;
if (module.virtual) {
map = generateSourceMapForVirtualModule(module);
}
if (options.excludeSource) {
map = _.extend({}, map, {sourcesContent: []});
}
result.sections.push({
offset: { line: line, column: 0 },
map: map,
});
line += module.code.split('\n').length;
});
return result;
};
Package.prototype.getSourceMap = function(options) {
this._assertFinalized();
options = options || {};
if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options);
}
var mappings = this._getMappings();
var map = {
file: 'bundle.js',
@ -168,13 +213,14 @@ Package.prototype._getMappings = function() {
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
var line = 'AACA';
var moduleLines = Object.create(null);
var mappings = '';
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var transformedCode = module.transformedCode;
var code = module.code;
var lastCharNewLine = false;
module.lines = 0;
for (var t = 0; t < transformedCode.length; t++) {
moduleLines[module.sourcePath] = 0;
for (var t = 0; t < code.length; t++) {
if (t === 0 && i === 0) {
mappings += firstLine;
} else if (t === 0) {
@ -183,13 +229,15 @@ Package.prototype._getMappings = function() {
// This is the only place were we actually don't know the mapping ahead
// of time. When it's a new module (and not the first) the lineno
// mapping is 0 (current) - number of lines in prev module.
mappings += base64VLQ.encode(0 - modules[i - 1].lines);
mappings += base64VLQ.encode(
0 - moduleLines[modules[i - 1].sourcePath]
);
mappings += 'A';
} else if (lastCharNewLine) {
module.lines++;
moduleLines[module.sourcePath]++;
mappings += line;
}
lastCharNewLine = transformedCode[t] === '\n';
lastCharNewLine = code[t] === '\n';
if (lastCharNewLine) {
mappings += ';';
}
@ -218,7 +266,25 @@ Package.prototype.getDebugInfo = function() {
this._modules.map(function(m) {
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
_.escape(m.transformedCode) + '</pre></code></div>';
_.escape(m.code) + '</pre></code></div>';
}).join('\n'),
].join('\n');
};
function generateSourceMapForVirtualModule(module) {
// All lines map 1-to-1
var mappings = 'AAAA;';
for (var i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
}
return {
version: 3,
sources: [ module.sourcePath ],
names: [],
mappings: mappings,
file: module.sourcePath,
sourcesContent: [ module.code ],
};
}

View File

@ -13,11 +13,13 @@ jest.autoMockOff();
var SourceMapGenerator = require('source-map').SourceMapGenerator;
describe('Package', function() {
var ModuleTransport;
var Package;
var ppackage;
beforeEach(function() {
Package = require('../Package');
ModuleTransport = require('../../lib/ModuleTransport');
ppackage = new Package('test_url');
ppackage.getSourceMap = jest.genMockFn().mockImpl(function() {
return 'test-source-map';
@ -26,8 +28,17 @@ describe('Package', function() {
describe('source package', function() {
it('should create a package and get the source', function() {
ppackage.addModule('transformed foo;', 'source foo', 'foo path');
ppackage.addModule('transformed bar;', 'source bar', 'bar path');
ppackage.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
}));
ppackage.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
}));
ppackage.finalize({});
expect(ppackage.getSource()).toBe([
'transformed foo;',
@ -37,8 +48,18 @@ describe('Package', function() {
});
it('should create a package and add run module code', function() {
ppackage.addModule('transformed foo;', 'source foo', 'foo path');
ppackage.addModule('transformed bar;', 'source bar', 'bar path');
ppackage.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
ppackage.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path'
}));
ppackage.setMainModuleId('foo');
ppackage.finalize({runMainModule: true});
expect(ppackage.getSource()).toBe([
@ -59,7 +80,11 @@ describe('Package', function() {
return minified;
};
ppackage.addModule('transformed foo;', 'source foo', 'foo path');
ppackage.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
ppackage.finalize();
expect(ppackage.getMinifiedSourceAndMap()).toBe(minified);
});
@ -68,13 +93,104 @@ describe('Package', function() {
describe('sourcemap package', function() {
it('should create sourcemap', function() {
var p = new Package('test_url');
p.addModule('transformed foo;\n', 'source foo', 'foo path');
p.addModule('transformed bar;\n', 'source bar', 'bar path');
p.addModule(new ModuleTransport({
code: [
'transformed foo',
'transformed foo',
'transformed foo',
].join('\n'),
sourceCode: [
'source foo',
'source foo',
'source foo',
].join('\n'),
sourcePath: 'foo path',
}));
p.addModule(new ModuleTransport({
code: [
'transformed bar',
'transformed bar',
'transformed bar',
].join('\n'),
sourceCode: [
'source bar',
'source bar',
'source bar',
].join('\n'),
sourcePath: 'bar path',
}));
p.setMainModuleId('foo');
p.finalize({runMainModule: true});
var s = p.getSourceMap();
expect(s).toEqual(genSourceMap(p._modules));
});
it('should combine sourcemaps', function() {
var p = new Package('test_url');
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
map: {name: 'sourcemap foo'},
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
map: {name: 'sourcemap bar'},
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
p.addModule(new ModuleTransport({
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
}));
p.setMainModuleId('foo');
p.finalize({runMainModule: true});
var s = p.getSourceMap();
expect(s).toEqual({
file: 'bundle.js',
version: 3,
sections: [
{ offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } },
{ offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } },
{
offset: {
column: 0,
line: 4
},
map: {
file: 'image.png',
mappings: 'AAAA;AACA;',
names: {},
sources: [ 'image.png' ],
sourcesContent: ['image module;\nimage module;'],
version: 3,
}
},
{
offset: {
column: 0,
line: 6
},
map: {
file: 'RunMainModule.js',
mappings: 'AAAA;',
names: {},
sources: [ 'RunMainModule.js' ],
sourcesContent: [';require("foo");'],
version: 3,
}
}
],
});
});
});
describe('getAssets()', function() {
@ -95,7 +211,7 @@ describe('Package', function() {
var packageLineNo = 0;
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var transformedCode = module.transformedCode;
var transformedCode = module.code;
var sourcePath = module.sourcePath;
var sourceCode = module.sourceCode;
var transformedLineCount = 0;

View File

@ -13,6 +13,7 @@ jest
.dontMock('path')
.dontMock('os')
.dontMock('underscore')
.dontMock('../../lib/ModuleTransport')
.setMock('uglify-js')
.dontMock('../');
@ -93,6 +94,7 @@ describe('Packager', function() {
.mockImpl(function(path) {
return Promise.resolve({
code: 'transformed ' + path,
map: 'sourcemap ' + path,
sourceCode: 'source ' + path,
sourcePath: path
});
@ -117,16 +119,19 @@ describe('Packager', function() {
return packager.package('/root/foo.js', true, 'source_map_url')
.then(function(p) {
expect(p.addModule.mock.calls[0]).toEqual([
'lol transformed /root/foo.js lol',
'source /root/foo.js',
'/root/foo.js'
]);
expect(p.addModule.mock.calls[1]).toEqual([
'lol transformed /root/bar.js lol',
'source /root/bar.js',
'/root/bar.js'
]);
expect(p.addModule.mock.calls[0][0]).toEqual({
code: 'lol transformed /root/foo.js lol',
map: 'sourcemap /root/foo.js',
sourceCode: 'source /root/foo.js',
sourcePath: '/root/foo.js',
});
expect(p.addModule.mock.calls[1][0]).toEqual({
code: 'lol transformed /root/bar.js lol',
map: 'sourcemap /root/bar.js',
sourceCode: 'source /root/bar.js',
sourcePath: '/root/bar.js'
});
var imgModule_DEPRECATED = {
__packager_asset: true,
@ -138,15 +143,15 @@ describe('Packager', function() {
deprecated: true,
};
expect(p.addModule.mock.calls[2]).toEqual([
'lol module.exports = ' +
expect(p.addModule.mock.calls[2][0]).toEqual({
code: 'lol module.exports = ' +
JSON.stringify(imgModule_DEPRECATED) +
'; lol',
'module.exports = ' +
sourceCode: 'module.exports = ' +
JSON.stringify(imgModule_DEPRECATED) +
';',
'/root/img/img.png'
]);
sourcePath: '/root/img/img.png'
});
var imgModule = {
__packager_asset: true,
@ -160,21 +165,21 @@ describe('Packager', function() {
type: 'png',
};
expect(p.addModule.mock.calls[3]).toEqual([
'lol module.exports = ' +
expect(p.addModule.mock.calls[3][0]).toEqual({
code: 'lol module.exports = ' +
JSON.stringify(imgModule) +
'; lol',
'module.exports = ' +
sourceCode: 'module.exports = ' +
JSON.stringify(imgModule) +
';',
'/root/img/new_image.png'
]);
sourcePath: '/root/img/new_image.png'
});
expect(p.addModule.mock.calls[4]).toEqual([
'lol module.exports = {"json":true}; lol',
'module.exports = {"json":true};',
'/root/file.json'
]);
expect(p.addModule.mock.calls[4][0]).toEqual({
code: 'lol module.exports = {"json":true}; lol',
sourceCode: 'module.exports = {"json":true};',
sourcePath: '/root/file.json'
});
expect(p.finalize.mock.calls[0]).toEqual([
{runMainModule: true}
@ -189,5 +194,4 @@ describe('Packager', function() {
]);
});
});
});

View File

@ -14,9 +14,9 @@ var path = require('path');
var Promise = require('bluebird');
var Transformer = require('../JSTransformer');
var DependencyResolver = require('../DependencyResolver');
var _ = require('underscore');
var Package = require('./Package');
var Activity = require('../Activity');
var ModuleTransport = require('../lib/ModuleTransport');
var declareOpts = require('../lib/declareOpts');
var imageSize = require('image-size');
@ -125,12 +125,8 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
.then(function(transformedModules) {
Activity.endEvent(transformEventId);
transformedModules.forEach(function(transformed) {
ppackage.addModule(
transformed.code,
transformed.sourceCode,
transformed.sourcePath
);
transformedModules.forEach(function(moduleTransport) {
ppackage.addModule(moduleTransport);
});
ppackage.finalize({ runMainModule: runModule });
@ -163,11 +159,13 @@ Packager.prototype._transformModule = function(ppackage, module) {
var resolver = this._resolver;
return transform.then(function(transformed) {
return _.extend(
{},
transformed,
{code: resolver.wrapModule(module, transformed.code)}
);
var code = resolver.wrapModule(module, transformed.code);
return new ModuleTransport({
code: code,
map: transformed.map,
sourceCode: transformed.sourceCode,
sourcePath: transformed.sourcePath,
});
});
};
@ -191,11 +189,12 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) {
var code = 'module.exports = ' + JSON.stringify(img) + ';';
return {
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
};
virtual: true,
});
});
};
@ -222,11 +221,12 @@ Packager.prototype.generateAssetModule = function(ppackage, module) {
var code = 'module.exports = ' + JSON.stringify(img) + ';';
return {
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
};
virtual: true,
});
});
};
@ -234,11 +234,12 @@ function generateJSONModule(module) {
return readFile(module.path).then(function(data) {
var code = 'module.exports = ' + data.toString('utf8') + ';';
return {
return new ModuleTransport({
code: code,
sourceCode: code,
sourcePath: module.path,
};
virtual: true,
});
});
}

View File

@ -0,0 +1,38 @@
/**
* 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';
function ModuleTransport(data) {
assertExists(data, 'code');
this.code = data.code;
assertExists(data, 'sourceCode');
this.sourceCode = data.sourceCode;
assertExists(data, 'sourcePath');
this.sourcePath = data.sourcePath;
this.virtual = data.virtual;
if (this.virtual && data.map) {
throw new Error('Virtual modules cannot have source maps');
}
this.map = data.map;
Object.freeze(this);
}
module.exports = ModuleTransport;
function assertExists(obj, field) {
if (obj[field] == null) {
throw new Error('Modules must have `' + field + '`');
}
}