diff --git a/Libraries/Components/View/FabricView.js b/Libraries/Components/View/FabricView.js new file mode 100644 index 00000000000..4d686f7beb2 --- /dev/null +++ b/Libraries/Components/View/FabricView.js @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @providesModule FabricView + * @flow + * @format + */ +'use strict'; + +/** + * This is a temporary fork of View.js for Fabric purpose. + * Do not use outside of Fabric tree. + */ + +const Platform = require('Platform'); +const React = require('React'); +const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const ViewPropTypes = require('ViewPropTypes'); +const {NativeMethodsMixin} = require('ReactFabricInternals'); +const {ViewContextTypes} = require('ViewContext'); + +const createReactClass = require('create-react-class'); +const invariant = require('fbjs/lib/invariant'); +const requireFabricComponent = require('requireFabricComponent'); + +import type {ViewProps} from 'ViewPropTypes'; +import type {ViewChildContext} from 'ViewContext'; + +export type Props = ViewProps; + +/** + * The most fundamental component for building a UI. + * + * See http://facebook.github.io/react-native/docs/view.html + */ +const View = createReactClass({ + displayName: 'View', + // TODO: We should probably expose the mixins, viewConfig, and statics publicly. For example, + // one of the props is of type AccessibilityComponentType. That is defined as a const[] above, + // but it is not rendered by the docs, since `statics` below is not rendered. So its Possible + // values had to be hardcoded. + mixins: [NativeMethodsMixin], + + // `propTypes` should not be accessed directly on View since this wrapper only + // exists for DEV mode. However it's important for them to be declared. + // If the object passed to `createClass` specifies `propTypes`, Flow will + // create a static type from it. + propTypes: ViewPropTypes, + + /** + * `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We + * make `this` look like an actual native component class. + */ + viewConfig: { + uiViewClassName: 'RCTView', + validAttributes: ReactNativeViewAttributes.RCTView, + }, + + childContextTypes: ViewContextTypes, + + getChildContext(): ViewChildContext { + return { + isInAParentText: false, + }; + }, + + render() { + invariant( + !(this.context.isInAParentText && Platform.OS === 'android'), + 'Nesting of within is not supported on Android.', + ); + + // WARNING: This method will not be used in production mode as in that mode we + // replace wrapper component View with generated native wrapper RCTView. Avoid + // adding functionality this component that you'd want to be available in both + // dev and prod modes. + return ; + }, +}); + +const RCTView = requireFabricComponent('RCTView', View, { + nativeOnly: { + nativeBackgroundAndroid: true, + nativeForegroundAndroid: true, + }, + fabric: true, +}); + +if (__DEV__) { + const UIManager = require('UIManager'); + const viewConfig = + (UIManager.viewConfigs && UIManager.viewConfigs.RCTView) || {}; + for (const prop in viewConfig.nativeProps) { + const viewAny: any = View; // Appease flow + if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) { + throw new Error( + 'View is missing propType for native prop `' + prop + '`', + ); + } + } +} + +let ViewToExport = RCTView; +if (__DEV__) { + ViewToExport = View; +} + +// No one should depend on the DEV-mode createClass View wrapper. +module.exports = ((ViewToExport: any): typeof RCTView); diff --git a/Libraries/ReactNative/AppContainer.js b/Libraries/ReactNative/AppContainer.js index cb1ea048e4a..3060d6de450 100644 --- a/Libraries/ReactNative/AppContainer.js +++ b/Libraries/ReactNative/AppContainer.js @@ -27,6 +27,7 @@ type Props = {| * suppresses an error when upgrading Flow's support for React. To see the * error delete this comment and run Flow. */ children?: React.Children, + fabric?: boolean, rootTag: number, WrapperComponent?: ?React.ComponentType<*>, |}; @@ -90,7 +91,8 @@ class AppContainer extends React.Component { render(): React.Node { let yellowBox = null; if (__DEV__) { - if (!global.__RCTProfileIsProfiling) { + if (!global.__RCTProfileIsProfiling && !this.props.fabric) { + // TODO: Fabric doesn't support YellowBox. const YellowBox = require('YellowBox'); yellowBox = ; } diff --git a/Libraries/ReactNative/FabricUIManager.js b/Libraries/ReactNative/FabricUIManager.js index 641df41755f..602b5459101 100644 --- a/Libraries/ReactNative/FabricUIManager.js +++ b/Libraries/ReactNative/FabricUIManager.js @@ -10,53 +10,29 @@ */ 'use strict'; -// TODO: fix the types -type Node = number; -type NodeSet = number; +// TODO: type these properly. +type Node = {}; +type NodeSet = Array; type NodeProps = {}; +type InstanceHandle = {}; type Spec = {| +createNode: ( reactTag: number, viewName: string, rootTag: number, props: NodeProps, - instanceHandle: number, + instanceHandle: InstanceHandle, ) => Node, +cloneNode: (node: Node) => Node, +cloneNodeWithNewChildren: (node: Node) => Node, +cloneNodeWithNewProps: (node: Node, newProps: NodeProps) => Node, +cloneNodeWithNewChildrenAndProps: (node: Node, newProps: NodeProps) => Node, + +createChildSet: (rootTag: number) => NodeSet, +appendChild: (parentNode: Node, child: Node) => Node, +appendChildToSet: (childSet: NodeSet, child: Node) => void, +completeRoot: (rootTag: number, childSet: NodeSet) => void, |}; -const NativeFabricUIManager: Spec = require('NativeModules').FabricUIManager; - -const FabricUIManager: Spec = { - createNode( - reactTag: number, - viewName: string, - rootTag: number, - props: NodeProps, - instanceHandle: number, - ): number { - return NativeFabricUIManager.createNode( - reactTag, - viewName, - rootTag, - props, - 0, // TODO: instanceHandle is cannot be JSON serialized. - ); - }, - cloneNode: NativeFabricUIManager.cloneNode, - cloneNodeWithNewChildren: NativeFabricUIManager.cloneNodeWithNewChildren, - cloneNodeWithNewProps: NativeFabricUIManager.cloneNodeWithNewProps, - cloneNodeWithNewChildrenAndProps: - NativeFabricUIManager.cloneNodeWithNewChildrenAndProps, - appendChild: NativeFabricUIManager.appendChild, - appendChildToSet: NativeFabricUIManager.appendChildToSet, - completeRoot: NativeFabricUIManager.completeRoot, -}; +const FabricUIManager: ?Spec = global.nativeFabricUIManager; module.exports = FabricUIManager; diff --git a/Libraries/ReactNative/renderFabricSurface.js b/Libraries/ReactNative/renderFabricSurface.js index f8acdd2c8dc..841a079b1d0 100644 --- a/Libraries/ReactNative/renderFabricSurface.js +++ b/Libraries/ReactNative/renderFabricSurface.js @@ -30,7 +30,10 @@ function renderFabricSurface( invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag); let renderable = ( - + ); diff --git a/Libraries/ReactNative/requireFabricComponent.js b/Libraries/ReactNative/requireFabricComponent.js new file mode 100644 index 00000000000..1544c6fdff9 --- /dev/null +++ b/Libraries/ReactNative/requireFabricComponent.js @@ -0,0 +1,240 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @providesModule requireFabricComponent + * @flow + * @format + */ +'use strict'; + +const Platform = require('Platform'); +const { + ReactNativeBridgeEventPlugin, + createReactNativeComponentClass, +} = require('ReactFabricInternals'); +const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +const UIManager = require('UIManager'); + +const insetsDiffer = require('insetsDiffer'); +const matricesDiffer = require('matricesDiffer'); +const pointsDiffer = require('pointsDiffer'); +const processColor = require('processColor'); +const resolveAssetSource = require('resolveAssetSource'); +const sizesDiffer = require('sizesDiffer'); +const verifyPropTypes = require('verifyPropTypes'); +const invariant = require('fbjs/lib/invariant'); +const warning = require('fbjs/lib/warning'); + +/** + * Used to create React components that directly wrap native component + * implementations. Config information is extracted from data exported from the + * UIManager module. You should also wrap the native component in a + * hand-written component with full propTypes definitions and other + * documentation - pass the hand-written component in as `componentInterface` to + * verify all the native props are documented via `propTypes`. + * + * If some native props shouldn't be exposed in the wrapper interface, you can + * pass null for `componentInterface` and call `verifyPropTypes` directly + * with `nativePropsToIgnore`; + * + * Common types are lined up with the appropriate prop differs with + * `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`. + */ +import type {ComponentInterface} from 'verifyPropTypes'; + +let hasAttachedDefaultEventTypes: boolean = false; + +function requireNativeComponent( + viewName: string, + componentInterface?: ?ComponentInterface, + extraConfig?: ?{nativeOnly?: Object}, +): React$ComponentType | string { + function attachDefaultEventTypes(viewConfig: any) { + if (Platform.OS === 'android') { + // This is supported on Android platform only, + // as lazy view managers discovery is Android-specific. + if (UIManager.ViewManagerNames) { + // Lazy view managers enabled. + viewConfig = merge(viewConfig, UIManager.getDefaultEventTypes()); + } else { + viewConfig.bubblingEventTypes = merge( + viewConfig.bubblingEventTypes, + UIManager.genericBubblingEventTypes, + ); + viewConfig.directEventTypes = merge( + viewConfig.directEventTypes, + UIManager.genericDirectEventTypes, + ); + } + } + } + + function merge(destination: ?Object, source: ?Object): ?Object { + if (!source) { + return destination; + } + if (!destination) { + return source; + } + + for (const key in source) { + if (!source.hasOwnProperty(key)) { + continue; + } + + var sourceValue = source[key]; + if (destination.hasOwnProperty(key)) { + const destinationValue = destination[key]; + if ( + typeof sourceValue === 'object' && + typeof destinationValue === 'object' + ) { + sourceValue = merge(destinationValue, sourceValue); + } + } + destination[key] = sourceValue; + } + return destination; + } + + // Don't load the ViewConfig from UIManager until it's needed for rendering. + // Lazy-loading this can help avoid Prepack deopts. + function getViewConfig() { + const viewConfig = UIManager[viewName]; + + invariant( + viewConfig != null && !viewConfig.NativeProps != null, + 'Native component for "%s" does not exist', + viewName, + ); + + viewConfig.uiViewClassName = viewName; + viewConfig.validAttributes = {}; + + // ReactNative `View.propTypes` have been deprecated in favor of + // `ViewPropTypes`. In their place a temporary getter has been added with a + // deprecated warning message. Avoid triggering that warning here by using + // temporary workaround, __propTypesSecretDontUseThesePlease. + // TODO (bvaughn) Revert this particular change any time after April 1 + if (componentInterface) { + viewConfig.propTypes = + typeof componentInterface.__propTypesSecretDontUseThesePlease === + 'object' + ? componentInterface.__propTypesSecretDontUseThesePlease + : componentInterface.propTypes; + } else { + viewConfig.propTypes = null; + } + + let baseModuleName = viewConfig.baseModuleName; + let bubblingEventTypes = viewConfig.bubblingEventTypes; + let directEventTypes = viewConfig.directEventTypes; + let nativeProps = viewConfig.NativeProps; + while (baseModuleName) { + const baseModule = UIManager[baseModuleName]; + if (!baseModule) { + warning(false, 'Base module "%s" does not exist', baseModuleName); + baseModuleName = null; + } else { + bubblingEventTypes = { + ...baseModule.bubblingEventTypes, + ...bubblingEventTypes, + }; + directEventTypes = { + ...baseModule.directEventTypes, + ...directEventTypes, + }; + nativeProps = { + ...baseModule.NativeProps, + ...nativeProps, + }; + baseModuleName = baseModule.baseModuleName; + } + } + + viewConfig.bubblingEventTypes = bubblingEventTypes; + viewConfig.directEventTypes = directEventTypes; + + for (const key in nativeProps) { + let useAttribute = false; + const attribute = {}; + + const differ = TypeToDifferMap[nativeProps[key]]; + if (differ) { + attribute.diff = differ; + useAttribute = true; + } + + const processor = TypeToProcessorMap[nativeProps[key]]; + if (processor) { + attribute.process = processor; + useAttribute = true; + } + + viewConfig.validAttributes[key] = useAttribute ? attribute : true; + } + + // Unfortunately, the current set up puts the style properties on the top + // level props object. We also need to add the nested form for API + // compatibility. This allows these props on both the top level and the + // nested style level. TODO: Move these to nested declarations on the + // native side. + viewConfig.validAttributes.style = ReactNativeStyleAttributes; + + if (__DEV__) { + componentInterface && + verifyPropTypes( + componentInterface, + viewConfig, + extraConfig && extraConfig.nativeOnly, + ); + } + + if (!hasAttachedDefaultEventTypes) { + attachDefaultEventTypes(viewConfig); + hasAttachedDefaultEventTypes = true; + } + + // Register this view's event types with the ReactNative renderer. + // This enables view managers to be initialized lazily, improving perf, + // While also enabling 3rd party components to define custom event types. + ReactNativeBridgeEventPlugin.processEventTypes(viewConfig); + + return viewConfig; + } + + return createReactNativeComponentClass(viewName, getViewConfig); +} + +const TypeToDifferMap = { + // iOS Types + CATransform3D: matricesDiffer, + CGPoint: pointsDiffer, + CGSize: sizesDiffer, + UIEdgeInsets: insetsDiffer, + // Android Types + // (not yet implemented) +}; + +function processColorArray(colors: ?Array): ?Array { + return colors && colors.map(processColor); +} + +const TypeToProcessorMap = { + // iOS Types + CGColor: processColor, + CGColorArray: processColorArray, + UIColor: processColor, + UIColorArray: processColorArray, + CGImage: resolveAssetSource, + UIImage: resolveAssetSource, + RCTImageSource: resolveAssetSource, + // Android Types + Color: processColor, + ColorArray: processColorArray, +}; + +module.exports = requireNativeComponent; diff --git a/Libraries/Renderer/ReactFabric-dev.js b/Libraries/Renderer/ReactFabric-dev.js index f3ef96ccd7f..81114b8c8f4 100644 --- a/Libraries/Renderer/ReactFabric-dev.js +++ b/Libraries/Renderer/ReactFabric-dev.js @@ -2760,7 +2760,8 @@ var ReactNativeGlobalResponderHandler = { /** * Register the event emitter with the native bridge */ -RCTEventEmitter.register(ReactNativeEventEmitter); +// TODO: This event emitter is interfering with the existing ReactNative renderer. +// RCTEventEmitter.register(ReactNativeEventEmitter); /** * Inject module for resolving DOM hierarchy and plugin ordering. diff --git a/Libraries/Renderer/ReactFabric-prod.js b/Libraries/Renderer/ReactFabric-prod.js index d4d20d3ac85..e25c7162a16 100644 --- a/Libraries/Renderer/ReactFabric-prod.js +++ b/Libraries/Renderer/ReactFabric-prod.js @@ -1140,7 +1140,8 @@ var ReactNativeEventEmitter = Object.freeze({ } } }); -RCTEventEmitter.register(ReactNativeEventEmitter); +// TODO: This event emitter is interfering with the existing ReactNative renderer. +// RCTEventEmitter.register(ReactNativeEventEmitter); injection.injectEventPluginOrder([ "ResponderEventPlugin", "ReactNativeBridgeEventPlugin" diff --git a/Libraries/Renderer/shims/ReactFabricInternals.js b/Libraries/Renderer/shims/ReactFabricInternals.js new file mode 100644 index 00000000000..d360803412a --- /dev/null +++ b/Libraries/Renderer/shims/ReactFabricInternals.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @providesModule ReactFabricInternals + * @flow + * @format + */ + +'use strict'; + +const { + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, +} = require('ReactFabric'); + +import type {NativeMethodsMixinType} from 'ReactNativeTypes'; + +const { + NativeMethodsMixin, + ReactNativeBridgeEventPlugin, + createReactNativeComponentClass, +} = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + +module.exports = { + NativeMethodsMixin: ((NativeMethodsMixin: any): $Exact< + NativeMethodsMixinType, + >), + ReactNativeBridgeEventPlugin, + createReactNativeComponentClass, +}; diff --git a/React/Fabric/RCTFabricUIManager.mm b/React/Fabric/RCTFabricUIManager.mm index 88fb08e6025..71d2198b8eb 100644 --- a/React/Fabric/RCTFabricUIManager.mm +++ b/React/Fabric/RCTFabricUIManager.mm @@ -60,6 +60,11 @@ - (void)appendChildToSet:(NSMutableArray *)childSet childNode:(RCTShadowView *)childNode { + if (!childNode) { + // TODO: until this class is fully implemented, we may get nil from JS. + return; + } + [childSet addObject:childNode]; }