diff --git a/.flowconfig b/.flowconfig index 4af17e66aefe84..2baa79d4e4fd00 100644 --- a/.flowconfig +++ b/.flowconfig @@ -59,10 +59,6 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - experimental.well_formed_exports=true experimental.types_first=true experimental.abstract_locations=true @@ -90,4 +86,4 @@ untyped-import untyped-type-import [version] -^0.124.0 +^0.125.0 diff --git a/.flowconfig.android b/.flowconfig.android index db4bff3b3cfbc9..0e766ae9d8ffd4 100644 --- a/.flowconfig.android +++ b/.flowconfig.android @@ -62,10 +62,6 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - experimental.well_formed_exports=true experimental.types_first=true experimental.abstract_locations=true @@ -93,4 +89,4 @@ untyped-import untyped-type-import [version] -^0.124.0 +^0.125.0 diff --git a/.flowconfig.macos b/.flowconfig.macos index 9cf5a7d0dcb5bd..e03d7fb76c0e5d 100644 --- a/.flowconfig.macos +++ b/.flowconfig.macos @@ -89,4 +89,4 @@ untyped-import untyped-type-import [version] -^0.124.0 +^0.125.0 diff --git a/Libraries/Animated/src/Animated.js b/Libraries/Animated/src/Animated.js index 0891d75a3f3649..53dd63c4709566 100644 --- a/Libraries/Animated/src/Animated.js +++ b/Libraries/Animated/src/Animated.js @@ -21,7 +21,8 @@ import typeof AnimatedView from './components/AnimatedView'; const AnimatedMock = require('./AnimatedMock'); const AnimatedImplementation = require('./AnimatedImplementation'); -const Animated = ((Platform.isTesting +const Animated = ((Platform.isTesting || +(Platform.OS === 'android' && global.RN$Bridgeless) ? AnimatedMock : AnimatedImplementation): typeof AnimatedMock); diff --git a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js index 61e37c54bea434..27f7abbe7d1c2f 100644 --- a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +++ b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js @@ -183,7 +183,6 @@ export type NativeProps = $ReadOnly<{| /** * When `false`, it will prevent the soft keyboard from showing when the field is focused. * Defaults to `true`. - * @platform android */ showSoftInputOnFocus?: ?boolean, diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index dd82eaf0a7027f..034ce9b70031e8 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -423,7 +423,6 @@ type AndroidProps = $ReadOnly<{| /** * When `false`, it will prevent the soft keyboard from showing when the field is focused. * Defaults to `true`. - * @platform android */ showSoftInputOnFocus?: ?boolean, |}>; diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index c9c8532867ec03..a55164413f4bba 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -701,7 +701,7 @@ const TouchableMixin = { globalY: number, ) { //don't do anything UIManager failed to measure node - if (!(l > 0 || t > 0 || w > 0 || h > 0 || globalX > 0 || globalY > 0)) { + if (!l && !t && !w && !h && !globalX && !globalY) { return; } this.state.touchable.positionOnActivate && diff --git a/Libraries/Core/Devtools/getDevServer.js b/Libraries/Core/Devtools/getDevServer.js index 5f8dfe1fcc26dc..c60d0e51606035 100644 --- a/Libraries/Core/Devtools/getDevServer.js +++ b/Libraries/Core/Devtools/getDevServer.js @@ -13,10 +13,12 @@ import NativeSourceCode from '../../NativeModules/specs/NativeSourceCode'; let _cachedDevServerURL: ?string; +let _cachedFullBundleURL: ?string; const FALLBACK = 'http://localhost:8081/'; type DevServerInfo = { url: string, + fullBundleUrl: ?string, bundleLoadedFromServer: boolean, ... }; @@ -27,14 +29,15 @@ type DevServerInfo = { */ function getDevServer(): DevServerInfo { if (_cachedDevServerURL === undefined) { - const match = NativeSourceCode.getConstants().scriptURL.match( - /^https?:\/\/.*?\//, - ); + const scriptUrl = NativeSourceCode.getConstants().scriptURL; + const match = scriptUrl.match(/^https?:\/\/.*?\//); _cachedDevServerURL = match ? match[0] : null; + _cachedFullBundleURL = match ? scriptUrl : null; } return { url: _cachedDevServerURL || FALLBACK, + fullBundleUrl: _cachedFullBundleURL, bundleLoadedFromServer: _cachedDevServerURL !== null, }; } diff --git a/Libraries/Core/ExceptionsManager.js b/Libraries/Core/ExceptionsManager.js index fd6ce28ad6e732..a151d26c5d5ff1 100644 --- a/Libraries/Core/ExceptionsManager.js +++ b/Libraries/Core/ExceptionsManager.js @@ -76,7 +76,7 @@ function reportException( message = e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`; - const isHandledByLogBox = e.forceRedbox !== true; + const isHandledByLogBox = e.forceRedbox !== true && !global.RN$Bridgeless; const data = preprocessException({ message, diff --git a/Libraries/DeprecatedPropTypes/DeprecatedTextInputPropTypes.js b/Libraries/DeprecatedPropTypes/DeprecatedTextInputPropTypes.js index 275d607b94821d..52a4714d84e914 100644 --- a/Libraries/DeprecatedPropTypes/DeprecatedTextInputPropTypes.js +++ b/Libraries/DeprecatedPropTypes/DeprecatedTextInputPropTypes.js @@ -616,7 +616,6 @@ module.exports = { /** * When `false`, it will prevent the soft keyboard from showing when the field is focused. * Defaults to `true`. - * @platform android */ showSoftInputOnFocus: PropTypes.bool, }; diff --git a/Libraries/FBLazyVector/BUCK b/Libraries/FBLazyVector/BUCK index ea6c50f49ffb9e..d526718e2de05b 100644 --- a/Libraries/FBLazyVector/BUCK +++ b/Libraries/FBLazyVector/BUCK @@ -8,7 +8,7 @@ fb_apple_library( enable_exceptions = False, extension_api_only = True, frameworks = [], - labels = ["supermodule:ios/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], link_whole = False, visibility = ["PUBLIC"], ) diff --git a/Libraries/FBReactNativeSpec/BUCK b/Libraries/FBReactNativeSpec/BUCK index dcef02f8165068..3ab64ac8a7a14a 100644 --- a/Libraries/FBReactNativeSpec/BUCK +++ b/Libraries/FBReactNativeSpec/BUCK @@ -15,7 +15,7 @@ fb_apple_library( "Foundation", "UIKit", ], - labels = ["supermodule:ios/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], reexport_all_header_dependencies = True, deps = [ "//xplat/js/react-native-github:RCTTypeSafety", diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm index 5c242e76747f02..2276aeeed11a71 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm @@ -2301,39 +2301,6 @@ + (RCTManagedPointer *)JS_NativeStatusBarManagerIOS_SpecGetHeightCallbackResult: } // namespace react } // namespace facebook -@implementation RCTCxxConvert (NativeTimePickerAndroid_TimePickerOptions) -+ (RCTManagedPointer *)JS_NativeTimePickerAndroid_TimePickerOptions:(id)json -{ - return facebook::react::managedPointer(json); -} -@end -namespace facebook { - namespace react { - - - static facebook::jsi::Value __hostFunction_NativeTimePickerAndroidSpecJSI_open(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { - return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, "open", @selector(open:resolve:reject:), args, count); - } - - - NativeTimePickerAndroidSpecJSI::NativeTimePickerAndroidSpecJSI(const ObjCTurboModule::InitParams ¶ms) - : ObjCTurboModule(params) { - - methodMap_["open"] = MethodMetadata {1, __hostFunction_NativeTimePickerAndroidSpecJSI_open}; - - setMethodArgConversionSelector(@"open", 0, @"JS_NativeTimePickerAndroid_TimePickerOptions:"); - - - } - - } // namespace react -} // namespace facebook -@implementation RCTCxxConvert (NativeTimePickerAndroid_TimePickerResult) -+ (RCTManagedPointer *)JS_NativeTimePickerAndroid_TimePickerResult:(id)json -{ - return facebook::react::managedPointer(json); -} -@end namespace facebook { namespace react { @@ -2421,10 +2388,6 @@ + (RCTManagedPointer *)JS_NativeTimePickerAndroid_TimePickerResult:(id)json return static_cast(turboModule).invokeObjCMethod(rt, ArrayKind, "getDefaultEventTypes", @selector(getDefaultEventTypes), args, count); } - static facebook::jsi::Value __hostFunction_NativeUIManagerSpecJSI_playTouchSound(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { - return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "playTouchSound", @selector(playTouchSound), args, count); - } - static facebook::jsi::Value __hostFunction_NativeUIManagerSpecJSI_lazilyLoadView(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { return static_cast(turboModule).invokeObjCMethod(rt, ObjectKind, "lazilyLoadView", @selector(lazilyLoadView:), args, count); } @@ -2531,9 +2494,6 @@ + (RCTManagedPointer *)JS_NativeTimePickerAndroid_TimePickerResult:(id)json methodMap_["getDefaultEventTypes"] = MethodMetadata {0, __hostFunction_NativeUIManagerSpecJSI_getDefaultEventTypes}; - methodMap_["playTouchSound"] = MethodMetadata {0, __hostFunction_NativeUIManagerSpecJSI_playTouchSound}; - - methodMap_["lazilyLoadView"] = MethodMetadata {1, __hostFunction_NativeUIManagerSpecJSI_lazilyLoadView}; diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h index ec337621912eeb..29bd6a93843cf8 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h @@ -2370,63 +2370,6 @@ namespace facebook { }; } // namespace react } // namespace facebook - -namespace JS { - namespace NativeTimePickerAndroid { - struct TimePickerOptions { - folly::Optional hour() const; - folly::Optional minute() const; - folly::Optional is24Hour() const; - NSString *mode() const; - - TimePickerOptions(NSDictionary *const v) : _v(v) {} - private: - NSDictionary *_v; - }; - } -} - -@interface RCTCxxConvert (NativeTimePickerAndroid_TimePickerOptions) -+ (RCTManagedPointer *)JS_NativeTimePickerAndroid_TimePickerOptions:(id)json; -@end -@protocol NativeTimePickerAndroidSpec - -- (void)open:(JS::NativeTimePickerAndroid::TimePickerOptions &)options - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject; - -@end -namespace facebook { - namespace react { - /** - * ObjC++ class for module 'TimePickerAndroid' - */ - - class JSI_EXPORT NativeTimePickerAndroidSpecJSI : public ObjCTurboModule { - public: - NativeTimePickerAndroidSpecJSI(const ObjCTurboModule::InitParams ¶ms); - - }; - } // namespace react -} // namespace facebook - -namespace JS { - namespace NativeTimePickerAndroid { - struct TimePickerResult { - NSString *action() const; - double hour() const; - double minute() const; - - TimePickerResult(NSDictionary *const v) : _v(v) {} - private: - NSDictionary *_v; - }; - } -} - -@interface RCTCxxConvert (NativeTimePickerAndroid_TimePickerResult) -+ (RCTManagedPointer *)JS_NativeTimePickerAndroid_TimePickerResult:(id)json; -@end @protocol NativeTimingSpec - (void)createTimer:(double)callbackID @@ -2542,7 +2485,6 @@ namespace JS { - (NSDictionary *)getConstantsForViewManager:(NSString *)viewManagerName; - (NSArray *)getDefaultEventTypes; -- (void)playTouchSound; - (NSDictionary *)lazilyLoadView:(NSString *)name; - (void)createView:(NSNumber *)reactTag viewName:(NSString *)viewName @@ -3515,41 +3457,6 @@ inline JS::NativeStatusBarManagerIOS::Constants::Builder::Builder(const Input i) inline JS::NativeStatusBarManagerIOS::Constants::Builder::Builder(Constants i) : _factory(^{ return i.unsafeRawValue(); }) {} -inline folly::Optional JS::NativeTimePickerAndroid::TimePickerOptions::hour() const -{ - id const p = _v[@"hour"]; - return RCTBridgingToOptionalDouble(p); -} -inline folly::Optional JS::NativeTimePickerAndroid::TimePickerOptions::minute() const -{ - id const p = _v[@"minute"]; - return RCTBridgingToOptionalDouble(p); -} -inline folly::Optional JS::NativeTimePickerAndroid::TimePickerOptions::is24Hour() const -{ - id const p = _v[@"is24Hour"]; - return RCTBridgingToOptionalBool(p); -} -inline NSString *JS::NativeTimePickerAndroid::TimePickerOptions::mode() const -{ - id const p = _v[@"mode"]; - return RCTBridgingToString(p); -} -inline NSString *JS::NativeTimePickerAndroid::TimePickerResult::action() const -{ - id const p = _v[@"action"]; - return RCTBridgingToString(p); -} -inline double JS::NativeTimePickerAndroid::TimePickerResult::hour() const -{ - id const p = _v[@"hour"]; - return RCTBridgingToDouble(p); -} -inline double JS::NativeTimePickerAndroid::TimePickerResult::minute() const -{ - id const p = _v[@"minute"]; - return RCTBridgingToDouble(p); -} inline JS::NativeToastAndroid::Constants::Builder::Builder(const Input i) : _factory(^{ NSMutableDictionary *d = [NSMutableDictionary new]; auto SHORT = i.SHORT.get(); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 22b28309076ee1..e5ff46e73827bf 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -15,6 +15,7 @@ const React = require('react'); const ReactNative = require('../Renderer/shims/ReactNative'); // eslint-disable-line no-unused-vars const StyleSheet = require('../StyleSheet/StyleSheet'); +const ImageAnalyticsTagContext = require('./ImageAnalyticsTagContext').default; const flattenStyle = require('../StyleSheet/flattenStyle'); const resolveAssetSource = require('./resolveAssetSource'); @@ -124,14 +125,21 @@ let Image = (props: ImagePropsType, forwardedRef) => { } return ( - + + {analyticTag => { + return ( + + ); + }} + ); }; diff --git a/Libraries/Image/NativeImageEditor.js b/Libraries/Image/NativeImageEditor.js index ff334b897771a2..19dbf3cb6e52a2 100644 --- a/Libraries/Image/NativeImageEditor.js +++ b/Libraries/Image/NativeImageEditor.js @@ -1,5 +1,8 @@ /** - * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format @@ -39,7 +42,6 @@ export interface Spec extends TurboModule { +getConstants: () => {||}; +cropImage: ( uri: string, - // eslint-disable-next-line lint/react-native-modules cropData: Options, successCallback: (uri: string) => void, errorCallback: (error: string) => void, diff --git a/Libraries/Image/NativeImageStore.js b/Libraries/Image/NativeImageStore.js index 196b328f7464e6..cbd7b22fd02613 100644 --- a/Libraries/Image/NativeImageStore.js +++ b/Libraries/Image/NativeImageStore.js @@ -1,5 +1,8 @@ /** - * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format diff --git a/Libraries/Image/RCTImageLoader.mm b/Libraries/Image/RCTImageLoader.mm index 88ed783c52c1e7..90c7a3b5ef7287 100644 --- a/Libraries/Image/RCTImageLoader.mm +++ b/Libraries/Image/RCTImageLoader.mm @@ -635,7 +635,7 @@ - (RCTImageURLLoaderRequest *)_loadImageOrDataWithURLRequest:(NSURLRequest *)req } } }); - + return [[RCTImageURLLoaderRequest alloc] initWithRequestId:requestId imageURL:request.URL cancellationBlock:^{ BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1); if (alreadyCancelled) { @@ -875,12 +875,24 @@ - (void)trackURLImageVisibilityForRequest:(RCTImageURLLoaderRequest *)loaderRequ } } +- (void)trackURLImageRequestDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest +{ + if (!loaderRequest) { + return; + } + + id loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL]; + if ([loadHandler respondsToSelector:@selector(trackURLImageRequestDidDestroy:)]) { + [(id)loadHandler trackURLImageRequestDidDestroy:loaderRequest]; + } +} + - (void)trackURLImageDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest { if (!loaderRequest) { return; } - + id loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL]; if ([loadHandler respondsToSelector:@selector(trackURLImageDidDestroy:)]) { [(id)loadHandler trackURLImageDidDestroy:loaderRequest]; diff --git a/Libraries/Image/RCTImageLoaderWithAttributionProtocol.h b/Libraries/Image/RCTImageLoaderWithAttributionProtocol.h index 664f800179857b..5f02703ecf5b4d 100644 --- a/Libraries/Image/RCTImageLoaderWithAttributionProtocol.h +++ b/Libraries/Image/RCTImageLoaderWithAttributionProtocol.h @@ -45,6 +45,11 @@ RCT_EXTERN void RCTEnableImageLoadingPerfInstrumentation(BOOL enabled); */ - (void)trackURLImageVisibilityForRequest:(RCTImageURLLoaderRequest *)loaderRequest imageView:(RCTUIView *)imageView; // TODO(macOS GH#774) +/** + * Image instrumentation - notify that the request was cancelled. + */ +- (void)trackURLImageRequestDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest; + /** * Image instrumentation - notify that the native image view was destroyed. */ diff --git a/Libraries/Image/RCTImageURLLoaderWithAttribution.h b/Libraries/Image/RCTImageURLLoaderWithAttribution.h index 43b4dcd4f30ac4..69587aaf07a5f7 100644 --- a/Libraries/Image/RCTImageURLLoaderWithAttribution.h +++ b/Libraries/Image/RCTImageURLLoaderWithAttribution.h @@ -18,6 +18,7 @@ namespace react { struct ImageURLLoaderAttribution { int32_t nativeViewTag = 0; int32_t surfaceId = 0; + NSString *analyticTag; }; } // namespace react @@ -69,6 +70,11 @@ struct ImageURLLoaderAttribution { */ - (void)trackURLImageVisibilityForRequest:(RCTImageURLLoaderRequest *)loaderRequest imageView:(RCTUIView *)imageView; // TODO(macOS GH#774) +/** + * Image instrumentation - notify that the request was destroyed. + */ +- (void)trackURLImageRequestDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest; + /** * Image instrumentation - notify that the native image view was destroyed. */ diff --git a/Libraries/Image/RCTImageView.h b/Libraries/Image/RCTImageView.h index 5e3064f166be27..90c7e79e26f0ae 100644 --- a/Libraries/Image/RCTImageView.h +++ b/Libraries/Image/RCTImageView.h @@ -30,6 +30,7 @@ typedef NS_ENUM(NSInteger, UIImageRenderingMode) { @property (nonatomic, copy) NSArray *imageSources; @property (nonatomic, assign) CGFloat blurRadius; @property (nonatomic, assign) RCTResizeMode resizeMode; +@property (nonatomic, copy) NSString *internal_analyticTag; #if TARGET_OS_OSX // [TODO(macOS GH#774) @property (nonatomic, copy) NSColor *tintColor; diff --git a/Libraries/Image/RCTImageView.mm b/Libraries/Image/RCTImageView.mm index e41c45cc10cbb4..8a14c4c137ae95 100644 --- a/Libraries/Image/RCTImageView.mm +++ b/Libraries/Image/RCTImageView.mm @@ -317,28 +317,34 @@ - (void)setResizeMode:(RCTResizeMode)resizeMode } } +- (void)setInternal_analyticTag:(NSString *)internal_analyticTag { + if (_internal_analyticTag != internal_analyticTag) { + _internal_analyticTag = internal_analyticTag; + _needsReload = YES; + } +} + - (void)cancelImageLoad { - if (_loaderRequest.cancellationBlock) { - _loaderRequest.cancellationBlock(); - } - - _loaderRequest = nil; + [_loaderRequest cancel]; _pendingImageSource = nil; } -- (void)clearImage +- (void)cancelAndClearImageLoad { [self cancelImageLoad]; - self.image = nil; - _imageSource = nil; + + [_imageLoader trackURLImageRequestDidDestroy:_loaderRequest]; + _loaderRequest = nil; } #if !TARGET_OS_OSX // TODO(macOS GH#774) - (void)clearImageIfDetached { if (!self.window) { - [self clearImage]; + [self cancelAndClearImageLoad]; + self.image = nil; + _imageSource = nil; } } #endif // TODO(macOS GH#774) @@ -399,7 +405,7 @@ - (BOOL)shouldChangeImageSource - (void)reloadImage { - [self cancelImageLoad]; + [self cancelAndClearImageLoad]; _needsReload = NO; RCTImageSource *source = [self imageSourceForSize:self.frame.size]; @@ -454,13 +460,14 @@ - (void)reloadImage attribution:{ .nativeViewTag = [self.reactTag intValue], .surfaceId = [self.rootTag intValue], + .analyticTag = self.internal_analyticTag } progressBlock:progressHandler partialLoadBlock:partialLoadHandler completionBlock:completionHandler]; _loaderRequest = loaderRequest; } else { - [self clearImage]; + [self cancelAndClearImageLoad]; } } @@ -623,6 +630,7 @@ - (void)didMoveToWindow // prioritise image requests that are actually on-screen, this removes // requests that have gotten "stuck" from the queue, unblocking other images // from loading. + // Do not clear _loaderRequest because this component can be visible again without changing image source [self cancelImageLoad]; } else if ([self shouldChangeImageSource]) { [self reloadImage]; diff --git a/Libraries/Image/RCTImageViewManager.mm b/Libraries/Image/RCTImageViewManager.mm index 67d3a29d167194..92e24a6fa752ed 100644 --- a/Libraries/Image/RCTImageViewManager.mm +++ b/Libraries/Image/RCTImageViewManager.mm @@ -40,6 +40,7 @@ - (RCTPlatformView *)view // TODO(macOS GH#774) RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode) +RCT_EXPORT_VIEW_PROPERTY(internal_analyticTag, NSString) RCT_REMAP_VIEW_PROPERTY(source, imageSources, NSArray); RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView) { diff --git a/Libraries/Image/__tests__/__snapshots__/Image-test.js.snap b/Libraries/Image/__tests__/__snapshots__/Image-test.js.snap index 1bcd31396ff7d4..ddb928125e2145 100644 --- a/Libraries/Image/__tests__/__snapshots__/Image-test.js.snap +++ b/Libraries/Image/__tests__/__snapshots__/Image-test.js.snap @@ -12,6 +12,7 @@ exports[` should render as when mocked 1`] = ` exports[` should render as when not mocked 1`] = ` 0) { + event.persist(); this._hoverInDelayTimeout = setTimeout(() => { onHoverIn(event); }, delayHoverIn); @@ -627,6 +628,7 @@ export default class Pressability { this._config.delayHoverOut, ); if (delayHoverOut > 0) { + event.persist(); this._hoverInDelayTimeout = setTimeout(() => { onHoverOut(event); }, delayHoverOut); @@ -764,6 +766,7 @@ export default class Pressability { normalizeDelay(this._config.delayPressOut), ); if (delayPressOut > 0) { + event.persist(); this._pressOutDelayTimeout = setTimeout(() => { onPressOut(event); }, delayPressOut); @@ -787,16 +790,7 @@ export default class Pressability { } _measureCallback = (left, top, width, height, pageX, pageY) => { - if ( - !( - left > 0 || - top > 0 || - width > 0 || - height > 0 || - pageX > 0 || - pageY > 0 - ) - ) { + if (!left && !top && !width && !height && !pageX && !pageY) { return; } this._responderRegion = { diff --git a/Libraries/RCTRequired/BUCK b/Libraries/RCTRequired/BUCK index 396f41b3f98e50..18d34dc7750d91 100644 --- a/Libraries/RCTRequired/BUCK +++ b/Libraries/RCTRequired/BUCK @@ -7,5 +7,5 @@ fb_apple_library( contacts = ["oncall+react_native@xmail.facebook.com"], extension_api_only = True, frameworks = ["Foundation"], - labels = ["supermodule:ios/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], ) diff --git a/Libraries/ReactNative/DummyUIManager.js b/Libraries/ReactNative/DummyUIManager.js index bf918219c79496..ba816c1cb3c444 100644 --- a/Libraries/ReactNative/DummyUIManager.js +++ b/Libraries/ReactNative/DummyUIManager.js @@ -22,7 +22,6 @@ module.exports = { getConstants: (): {...} => ({}), getConstantsForViewManager: (viewManagerName: string) => {}, getDefaultEventTypes: (): Array<$FlowFixMe> => [], - playTouchSound: () => {}, lazilyLoadView: (name: string) => {}, createView: ( reactTag: ?number, diff --git a/Libraries/ReactNative/NativeUIManager.js b/Libraries/ReactNative/NativeUIManager.js index 717e63dbc12282..30e2d1f2c7d0df 100644 --- a/Libraries/ReactNative/NativeUIManager.js +++ b/Libraries/ReactNative/NativeUIManager.js @@ -17,7 +17,6 @@ export interface Spec extends TurboModule { +getConstants: () => Object; +getConstantsForViewManager: (viewManagerName: string) => Object; +getDefaultEventTypes: () => Array; - +playTouchSound: () => void; +lazilyLoadView: (name: string) => Object; // revisit return +createView: ( reactTag: ?number, diff --git a/Libraries/ReactNative/UIManagerProperties.js b/Libraries/ReactNative/UIManagerProperties.js index 3205d7ea252e2e..938e42eef9d4ad 100644 --- a/Libraries/ReactNative/UIManagerProperties.js +++ b/Libraries/ReactNative/UIManagerProperties.js @@ -40,7 +40,6 @@ module.exports = [ 'measureInWindow', 'measureLayout', 'measureLayoutRelativeToParent', - 'playTouchSound', 'removeRootView', 'removeSubviewsFromContainerWithID', 'replaceExistingNonRootView', diff --git a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 61d539f1e61717..b593c4c863476b 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UIEdgeInsets textContainerInset; #if !TARGET_OS_OSX // TODO(macOS GH#774) @property (nonatomic, strong, nullable) UIView *inputAccessoryView; +@property (nonatomic, strong, nullable) UIView *inputView; #endif // TODO(macOS GH#774) @property (nonatomic, weak, nullable) id textInputDelegate; @property (nonatomic, readonly) CGSize contentSize; diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.h b/Libraries/Text/TextInput/RCTBaseTextInputView.h index 75cde85e7a71d4..992d34b07c00c5 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.h +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.h @@ -57,6 +57,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy) NSString *inputAccessoryViewID; #if !TARGET_OS_OSX // TODO(macOS GH#774) @property (nonatomic, assign) UIKeyboardType keyboardType; +@property (nonatomic, assign) BOOL showSoftInputOnFocus; #endif // TODO(macOS GH#774) /** diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index ccabbb2e922da0..c1e4222f765173 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -348,6 +348,18 @@ - (void)setKeyboardType:(UIKeyboardType)keyboardType } } } + +- (void)setShowSoftInputOnFocus:(BOOL)showSoftInputOnFocus +{ + (void)_showSoftInputOnFocus; + if (showSoftInputOnFocus) { + // Resets to default keyboard. + self.backedTextInputView.inputView = nil; + } else { + // Hides keyboard, but keeps blinking cursor. + self.backedTextInputView.inputView = [[UIView alloc] init]; + } +} #endif // TODO(macOS GH#774) #pragma mark - RCTBackedTextInputDelegate diff --git a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m index 4f764b2c7bceb4..81def532f60c6a 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m @@ -58,6 +58,7 @@ @implementation RCTBaseTextInputViewManager RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL) RCT_EXPORT_NOT_OSX_VIEW_PROPERTY(clearTextOnFocus, BOOL) // TODO(macOS GH#774) RCT_REMAP_NOT_OSX_VIEW_PROPERTY(keyboardType, backedTextInputView.keyboardType, UIKeyboardType) // TODO(macOS GH#774) +RCT_EXPORT_NOT_OSX_VIEW_PROPERTY(showSoftInputOnFocus, BOOL) // TODO(macOS GH#774) RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber) RCT_EXPORT_NOT_OSX_VIEW_PROPERTY(selectTextOnFocus, BOOL) // TODO(macOS GH#774) RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection) diff --git a/Libraries/TimePickerAndroid/NativeTimePickerAndroid.js b/Libraries/TimePickerAndroid/NativeTimePickerAndroid.js deleted file mode 100644 index 46594ffdc3196e..00000000000000 --- a/Libraries/TimePickerAndroid/NativeTimePickerAndroid.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. - * - * @flow strict-local - * @format - */ - -'use strict'; - -import type {TurboModule} from '../TurboModule/RCTExport'; -import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; - -export type TimePickerOptions = {| - hour?: number, - minute?: number, - is24Hour?: boolean, - mode?: string, -|}; - -export type TimePickerResult = {| - action: string, - hour: number, - minute: number, -|}; - -export interface Spec extends TurboModule { - // eslint-disable-next-line lint/react-native-modules - +open: (options: TimePickerOptions) => Promise; -} - -export default (TurboModuleRegistry.get('TimePickerAndroid'): ?Spec); diff --git a/Libraries/Utilities/HMRClient.js b/Libraries/Utilities/HMRClient.js index ca6d8ef5a4b14e..e653ab89ff0ba5 100644 --- a/Libraries/Utilities/HMRClient.js +++ b/Libraries/Utilities/HMRClient.js @@ -16,6 +16,7 @@ const MetroHMRClient = require('metro/src/lib/bundle-modules/HMRClient'); const Platform = require('./Platform'); const prettyFormat = require('pretty-format'); +import getDevServer from '../Core/Devtools/getDevServer'; import NativeRedBox from '../NativeModules/specs/NativeRedBox'; import * as LogBoxData from '../LogBox/Data/LogBoxData'; import type {ExtendedError} from '../Core/Devtools/parseErrorStack'; @@ -159,8 +160,15 @@ const HMRClient: HMRClientNativeInterface = { const client = new MetroHMRClient(`ws://${wsHost}/hot`); hmrClient = client; + const {fullBundleUrl} = getDevServer(); pendingEntryPoints.push( - `ws://${wsHost}/hot?bundleEntry=${bundleEntry}&platform=${platform}`, + // HMRServer understands regular bundle URLs, so prefer that in case + // there are any important URL parameters we can't reconstruct from + // `setup()`'s arguments. + fullBundleUrl ?? + // The ws://.../hot?bundleEntry= format is an alternative to specifying + // a regular HTTP bundle URL. + `ws://${wsHost}/hot?bundleEntry=${bundleEntry}&platform=${platform}`, ); client.on('connection-error', e => { diff --git a/Libraries/Utilities/LoadingView.ios.js b/Libraries/Utilities/LoadingView.ios.js index ad2caad48310ea..bc423a822ae7c0 100644 --- a/Libraries/Utilities/LoadingView.ios.js +++ b/Libraries/Utilities/LoadingView.ios.js @@ -12,25 +12,38 @@ import processColor from '../StyleSheet/processColor'; import NativeDevLoadingView from './NativeDevLoadingView'; +import Appearance from './Appearance'; module.exports = { showMessage(message: string, type: 'load' | 'refresh') { if (NativeDevLoadingView) { - const loadColor = processColor('#404040'); - const refreshColor = processColor('#2584e8'); - const white = processColor('#ffffff'); + if (type === 'refresh') { + const backgroundColor = processColor('#2584e8'); + const textColor = processColor('#ffffff'); - NativeDevLoadingView.showMessage( - message, - typeof white === 'number' ? white : null, - type && type === 'load' - ? typeof loadColor === 'number' - ? loadColor - : null - : typeof refreshColor === 'number' - ? refreshColor - : null, - ); + NativeDevLoadingView.showMessage( + message, + typeof textColor === 'number' ? textColor : null, + typeof backgroundColor === 'number' ? backgroundColor : null, + ); + } else if (type === 'load') { + let backgroundColor; + let textColor; + + if (Appearance.getColorScheme() === 'dark') { + backgroundColor = processColor('#fafafa'); + textColor = processColor('#242526'); + } else { + backgroundColor = processColor('#404040'); + textColor = processColor('#ffffff'); + } + + NativeDevLoadingView.showMessage( + message, + typeof textColor === 'number' ? textColor : null, + typeof backgroundColor === 'number' ? backgroundColor : null, + ); + } } }, hide() { diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index 2a943a8961ac03..f316bea68611fc 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -651,10 +651,6 @@ const MatrixMath = { skew[0] = MatrixMath.v3Dot(row[0], row[1]); row[1] = MatrixMath.v3Combine(row[1], row[0], 1.0, -skew[0]); - // Compute XY shear factor and make 2nd row orthogonal to 1st. - skew[0] = MatrixMath.v3Dot(row[0], row[1]); - row[1] = MatrixMath.v3Combine(row[1], row[0], 1.0, -skew[0]); - // Now, compute Y scale and normalize 2nd row. scale[1] = MatrixMath.v3Length(row[1]); row[1] = MatrixMath.v3Normalize(row[1], scale[1]); diff --git a/Libraries/Utilities/ReactNativeTestTools.js b/Libraries/Utilities/ReactNativeTestTools.js index cd018a6fc5e3cc..f6dadab9b7da93 100644 --- a/Libraries/Utilities/ReactNativeTestTools.js +++ b/Libraries/Utilities/ReactNativeTestTools.js @@ -16,8 +16,8 @@ const React = require('react'); const ReactTestRenderer = require('react-test-renderer'); const ShallowRenderer = require('react-test-renderer/shallow'); -/* $FlowFixMe(>=0.122.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.122.0 was deployed. To see the error, delete this comment +/* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an error + * found when Flow v0.125.1 was deployed. To see the error, delete this comment * and run Flow. */ const shallowRenderer = new ShallowRenderer(); @@ -28,8 +28,8 @@ export type ReactTestInstance = $PropertyType; export type Predicate = (node: ReactTestInstance) => boolean; type $ReturnType = $Call<((...A) => Ret) => Ret, Fn>; -/* $FlowFixMe(>=0.122.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.122.0 was deployed. To see the error, delete this comment +/* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an error + * found when Flow v0.125.1 was deployed. To see the error, delete this comment * and run Flow. */ export type ReactTestRendererJSON = $ReturnType; @@ -56,12 +56,12 @@ function byClickable(): Predicate { // HACK: Find components that use `Pressability`. node.instance?.state?.pressability != null || // TODO: Remove this after deleting `Touchable`. - /* $FlowFixMe(>=0.122.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.122.0 was deployed. To see the error, delete + /* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an + * error found when Flow v0.125.1 was deployed. To see the error, delete * this comment and run Flow. */ (node.instance && - /* $FlowFixMe(>=0.122.0 site=react_native_fb) This comment suppresses - * an error found when Flow v0.122.0 was deployed. To see the error, + /* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses + * an error found when Flow v0.125.1 was deployed. To see the error, * delete this comment and run Flow. */ typeof node.instance.touchableHandlePress === 'function'), 'is clickable', @@ -77,8 +77,8 @@ function byTestID(testID: string): Predicate { function byTextMatching(regex: RegExp): Predicate { return withMessage( - /* $FlowFixMe(>=0.122.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.122.0 was deployed. To see the error, delete + /* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an + * error found when Flow v0.125.1 was deployed. To see the error, delete * this comment and run Flow. */ node => node.props && regex.exec(node.props.children), `text content matches ${regex.toString()}`, diff --git a/RNTester/Podfile.lock b/RNTester/Podfile.lock index a5767ec8323f91..81fbcd80bd1d8e 100644 --- a/RNTester/Podfile.lock +++ b/RNTester/Podfile.lock @@ -99,6 +99,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/ARTHeaders (1000.0.0): - glog @@ -107,6 +108,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/CoreModulesHeaders (1000.0.0): - glog @@ -115,6 +117,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/Default (1000.0.0): - glog @@ -122,6 +125,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/DevSupport (1000.0.0): - glog @@ -132,6 +136,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-jsinspector (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTActionSheetHeaders (1000.0.0): - glog @@ -140,6 +145,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTAnimationHeaders (1000.0.0): - glog @@ -148,6 +154,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTBlobHeaders (1000.0.0): - glog @@ -156,6 +163,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTImageHeaders (1000.0.0): - glog @@ -164,6 +172,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTLinkingHeaders (1000.0.0): - glog @@ -172,6 +181,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTNetworkHeaders (1000.0.0): - glog @@ -180,6 +190,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTPushNotificationHeaders (1000.0.0): - glog @@ -188,6 +199,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTSettingsHeaders (1000.0.0): - glog @@ -196,6 +208,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTTextHeaders (1000.0.0): - glog @@ -204,6 +217,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTVibrationHeaders (1000.0.0): - glog @@ -212,6 +226,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-Core/RCTWebSocket (1000.0.0): - glog @@ -220,6 +235,7 @@ PODS: - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) + - React-perflogger (= 1000.0.0) - Yoga - React-CoreModules (1000.0.0): - FBReactNativeSpec (= 1000.0.0) @@ -236,6 +252,7 @@ PODS: - RCT-Folly (= 2020.01.13.00) - React-callinvoker (= 1000.0.0) - React-jsinspector (= 1000.0.0) + - React-perflogger (= 1000.0.0) - React-runtimeexecutor (= 1000.0.0) - React-jsi (1000.0.0): - boost-for-react-native (= 1.63.0) @@ -254,7 +271,9 @@ PODS: - RCT-Folly (= 2020.01.13.00) - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) + - React-perflogger (= 1000.0.0) - React-jsinspector (1000.0.0) + - React-perflogger (1000.0.0) - React-RCTActionSheet (1000.0.0): - React-Core/RCTActionSheetHeaders (= 1000.0.0) - React-RCTAnimation (1000.0.0): @@ -342,6 +361,7 @@ PODS: - React-Core (= 1000.0.0) - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) + - React-perflogger (= 1000.0.0) - ReactCommon/turbomodule/samples (1000.0.0): - DoubleConversion - glog @@ -350,6 +370,7 @@ PODS: - React-Core (= 1000.0.0) - React-cxxreact (= 1000.0.0) - React-jsi (= 1000.0.0) + - React-perflogger (= 1000.0.0) - ReactCommon/turbomodule/core (= 1000.0.0) - Yoga (1.14.0) - YogaKit (1.18.1): @@ -394,6 +415,7 @@ DEPENDENCIES: - React-jsi (from `../ReactCommon/jsi`) - React-jsiexecutor (from `../ReactCommon/jsiexecutor`) - React-jsinspector (from `../ReactCommon/jsinspector`) + - React-perflogger (from `../ReactCommon/reactperflogger`) - React-RCTActionSheet (from `../Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../Libraries/NativeAnimation`) - React-RCTBlob (from `../Libraries/Blob`) @@ -461,6 +483,8 @@ EXTERNAL SOURCES: :path: "../ReactCommon/jsiexecutor" React-jsinspector: :path: "../ReactCommon/jsinspector" + React-perflogger: + :path: "../ReactCommon/reactperflogger" React-RCTActionSheet: :path: "../Libraries/ActionSheetIOS" React-RCTAnimation: @@ -499,8 +523,8 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f DoubleConversion: 2b45d0f8e156a5b02354c8a4062de64d41ccb4e0 - FBLazyVector: 98b5ed9e2c8215c32630adabb6c6ae8fd88b4808 - FBReactNativeSpec: a5a59afb795ccab0596667e248b5cdcb05cf86e2 + FBLazyVector: dac58e415545ed3aaa631054fe0447782a933865 + FBReactNativeSpec: d25a00a4a4d0392ed51a01fcdc33b3445bae2957 Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 @@ -511,33 +535,34 @@ SPEC CHECKSUMS: glog: 789873d01e4b200777d0a09bc23d548446758699 OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 RCT-Folly: 55d0039b24e192081ec0b2257f7bd9f42e382fb7 - RCTRequired: cd124a69447b4db403af1f4dc1d7c7cb617185a0 - RCTTypeSafety: 83da012f103c272792343e635da1b72936067b96 - React: e7aaeceb4fee9ffc841301330e753a1bd3e57437 - React-ART: 114994073be3e1257406f49d629222d94f5361bd - React-callinvoker: ebde245fca99f1f324793be8783dd6def592627c - React-Core: 4775366e8e941f063ee8463f86bf41613ec1a7b2 - React-CoreModules: ca9df8e2a54ccbaad588ffa772f71fbc8ba19279 - React-cxxreact: cfba7c9e9bd2d23b97af6467d5ecf390e932676d - React-jsi: b0857c300ffbf312d1b15e60a060ad7f5a2f978d - React-jsiexecutor: 93e0bb19579e3002775bc3efc0cbd81f20943be8 - React-jsinspector: 3526475c50fc3d942d6cdc02618f72a5f5510935 - React-RCTActionSheet: 23ed110450142634ede6b88ccbd2fb69aa892c52 - React-RCTAnimation: 383e388d729b39d0087b4986f0ebb728bac7eb02 - React-RCTBlob: 53fcb292e073976d7f6867841eee402770bfbf84 - React-RCTImage: 3ea30c5c2fd33f042aa768e6a1688046bbf59bda - React-RCTLinking: cb77eeacb78c1b3cd1c579c6597ea2bbe424945e - React-RCTNetwork: a34eabaa807c9163af30d4e4623b20e3136659bc - React-RCTPushNotification: 9f0a55cbaeccb9a10252d468fa73c7281aef680c - React-RCTSettings: 149f8ea51af3afb60f4a3a262698b94a0a9209aa - React-RCTTest: 7d56585d7cc88320631522ae29ffba082e12c377 - React-RCTText: 9b42805a9082c6e6df5b0deab2bdba14f1db0e3a - React-RCTVibration: c834fb5115f6d341b68a9595905bf253b617ab1d - React-runtimeexecutor: 576ce90cf13a65e00b35c3cbcdbe65aaddea8351 + RCTRequired: 212ed857d2e02aa7a950e2e9fd1e0e14bf36dac9 + RCTTypeSafety: cc7500b093bff4f94b61c64033c26f9c9b613ed6 + React: 1fbebfa33be059ef6f440ac79e568d7cc2f881db + React-ART: fda22d56018ca90ce94d0775f756acc88f700041 + React-callinvoker: 6b4291b2f7baa371074a21bc01c1ae3469159f26 + React-Core: 7587308afd5f755599465e6c8e0d111fe7294370 + React-CoreModules: 2845f7f2e5487b7146b8fcfb28603e7aba8c73dd + React-cxxreact: b79fc1d424cdedac912585efd2fb8481c963185b + React-jsi: 4ff577747833dbf79aadf814d0a7103b70566ba7 + React-jsiexecutor: 64210b838560d2aaf1ed69f514894961cf43f684 + React-jsinspector: 3fd9965e147de74ce261fdb58fef297b2973b84e + React-perflogger: 097bc54ba46a5733759303edb807834888c694de + React-RCTActionSheet: d0520c2146f744d28915281af40b8421c549a648 + React-RCTAnimation: 87c74e6bdb902190345b90df4878a018dd62d3f3 + React-RCTBlob: 1ed3cccb9eba04212a0779493902b4673605f79f + React-RCTImage: 1df1bc4d3f172c43c053d881befd4e189b58930e + React-RCTLinking: 35ce87cc530d4c9945d7c4f457a3d370f0c22361 + React-RCTNetwork: 450019d7b09925c10210be67268870761abc560a + React-RCTPushNotification: 3638092f4cc05336049fe360d45dfc64e1f662b4 + React-RCTSettings: 90fbb7c75497e6a68a60fd3d155cec3aa9ec3905 + React-RCTTest: 703fefa06f06aeeb412b2b4912c7104517bdf0a7 + React-RCTText: 34cd0c240c00bfe8b942c5a3038169754a902200 + React-RCTVibration: 2d2b494b2d8795b8382545bf31a686cc39056bc7 + React-runtimeexecutor: 56de50ce596344a7d1fbc697149b844183e9ff9b React-TurboModuleCxx-RNW: 18bb71af41fe34c8b12a56bef60aae7ee32b0817 - React-TurboModuleCxx-WinRTPort: d4886bfcdff8fb04d407e6a3b7ee8feb7cc2ad40 - ReactCommon: 4ae7a0aa58b93673a204fdd12ff4f8b079baf20e - Yoga: dd900352619ba7f891e6e505ceee59afc0012115 + React-TurboModuleCxx-WinRTPort: 892e11a61325fcab19632767b4e4d0fb6979ae33 + ReactCommon: 574163ae6b6797f54c228ad305bc7a52afa1cef9 + Yoga: ba8ee169a50208f5ab580225e339f8d7f6a46e22 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a PODFILE CHECKSUM: 7d43a928a9b9ad27329da110adbfadd923a39ba8 diff --git a/RNTester/README.md b/RNTester/README.md index 4efa9b297ab165..e286de8cb51702 100644 --- a/RNTester/README.md +++ b/RNTester/README.md @@ -14,8 +14,8 @@ Before running the app, make sure you ran: Both macOS and Xcode are required. -- Install CocoaPods. We installing CocoaPods using [Homebrew](http://brew.sh/): `brew install cocoapods` -- Run `cd RNTester; pod install` +- Install [Bundler](https://bundler.io/): `gem install bundler`. We use bundler to install the right version of [CocoaPods](https://cocoapods.org/) locally. +- Install Bundler and CocoaPods dependencies: `bundle install && bundle exec pod install` - Open the generated `RNTesterPods.xcworkspace`. This is not checked in, as it is generated by CocoaPods. Do not open `RNTesterPods.xcodeproj` directly. ### Running on Android diff --git a/RNTester/RNTester-macOS/AppDelegate.mm b/RNTester/RNTester-macOS/AppDelegate.mm index 6a938847502593..7951d625e53af2 100644 --- a/RNTester/RNTester-macOS/AppDelegate.mm +++ b/RNTester/RNTester-macOS/AppDelegate.mm @@ -98,6 +98,15 @@ - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge delegate:self jsInvoker:bridge.jsCallInvoker]; + +#if RCT_DEV + /** + * Eagerly initialize RCTDevMenu so CMD + d, CMD + i, and CMD + r work. + * This is a stop gap until we have a system to eagerly init Turbo Modules. + */ + [_turboModuleManager moduleForName:"RCTDevMenu"]; +#endif + __weak __typeof(self) weakSelf = self; return std::make_unique( facebook::react::RCTJSIExecutorRuntimeInstaller([weakSelf, bridge](facebook::jsi::Runtime &runtime) { @@ -132,11 +141,7 @@ - (Class)getModuleClassFromName:(const char *)name return _turboModulesProvider->getModule(name, jsInvoker); } -- (std::shared_ptr)getTurboModule:(const std::string &)name - instance:(id)instance - jsInvoker:(std::shared_ptr)jsInvoker - nativeInvoker:(std::shared_ptr)nativeInvoker - perfLogger:(id)perfLogger +- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { return nullptr; } diff --git a/RNTester/RNTester/AppDelegate.mm b/RNTester/RNTester/AppDelegate.mm index 7e6e7fd10182e0..df27fa1addf244 100644 --- a/RNTester/RNTester/AppDelegate.mm +++ b/RNTester/RNTester/AppDelegate.mm @@ -161,6 +161,15 @@ - (void)loadSourceForBridge:(RCTBridge *)bridge _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge delegate:self jsInvoker:bridge.jsCallInvoker]; + +#if RCT_DEV + /** + * Eagerly initialize RCTDevMenu so CMD + d, CMD + i, and CMD + r work. + * This is a stop gap until we have a system to eagerly init Turbo Modules. + */ + [_turboModuleManager moduleForName:"RCTDevMenu"]; +#endif + __weak __typeof(self) weakSelf = self; return std::make_unique( facebook::react::RCTJSIExecutorRuntimeInstaller([weakSelf, bridge](facebook::jsi::Runtime &runtime) { diff --git a/RNTester/RNTesterPods.xcodeproj/project.pbxproj b/RNTester/RNTesterPods.xcodeproj/project.pbxproj index b7a8859d7683d5..563893e2e592da 100644 --- a/RNTester/RNTesterPods.xcodeproj/project.pbxproj +++ b/RNTester/RNTesterPods.xcodeproj/project.pbxproj @@ -1558,6 +1558,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5BEC8567F3741044B6A5EFC5 /* Pods-RNTester.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CODE_SIGN_ENTITLEMENTS = RNTester/RNTester.entitlements; diff --git a/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m b/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m index 6d2a2b24ad1b8c..386565f6d2ee40 100644 --- a/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m +++ b/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m @@ -20,12 +20,12 @@ static NSURL *localhostBundleURL() { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=%@&dev=true&minify=false", testFile, kRCTPlatformName]]; // TODO(macOS ISS#3536887) + return [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=%@&dev=true&minify=false&app=com.apple.dt.xctest.tool", testFile, kRCTPlatformName]]; // TODO(macOS ISS#3536887) } static NSURL *ipBundleURL() { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://192.168.1.1:8081/%@.bundle?platform=%@&dev=true&minify=false", testFile, kRCTPlatformName]]; // TODO(macOS ISS#3536887) + return [NSURL URLWithString:[NSString stringWithFormat:@"http://192.168.1.1:8081/%@.bundle?platform=%@&dev=true&minify=false&app=com.apple.dt.xctest.tool", testFile, kRCTPlatformName]]; // TODO(macOS ISS#3536887) } @implementation NSBundle (RCTBundleURLProviderTests) diff --git a/RNTester/js/components/TextInlineView.js b/RNTester/js/components/TextInlineView.js index e5fcc68aaeac4c..a15762131b5d22 100644 --- a/RNTester/js/components/TextInlineView.js +++ b/RNTester/js/components/TextInlineView.js @@ -25,6 +25,21 @@ function Basic(): React.Node { ); } +function NestedTexts(): React.Node { + return ( + + This is the first row + + + This is a nested text + + with a Red View + + + + ); +} + function ClippedByText(): React.Node { return ( @@ -200,6 +215,7 @@ class ChangeInnerViewSize extends React.Component<*, ChangeSizeState> { module.exports = { Basic, + NestedTexts, ClippedByText, ChangeImageSize, ChangeViewSize, diff --git a/RNTester/js/examples/ScrollView/ScrollViewExample.js b/RNTester/js/examples/ScrollView/ScrollViewExample.js index c00ea24ea83ae3..b54a6811adfec4 100644 --- a/RNTester/js/examples/ScrollView/ScrollViewExample.js +++ b/RNTester/js/examples/ScrollView/ScrollViewExample.js @@ -287,6 +287,38 @@ if (Platform.OS === 'ios') { return ; }, }); + exports.examples.push({ + title: ' (centerContent = true)\n', + description: + 'ScrollView puts its content in the center if the content is smaller than scroll view', + render: function(): React.Node { + function CenterContentList(): React.Node { + return ( + + This should be in center. + + ); + } + return ; + }, + }); + exports.examples.push({ + title: ' (contentOffset = {x: 100, y: 0})\n', + description: 'Initial contentOffset can be set on ScrollView.', + render: function(): React.Node { + function CenterContentList(): React.Node { + return ( + + {ITEMS.map(createItemRow)} + + ); + } + return ; + }, + }); } class Item extends React.PureComponent<{| diff --git a/RNTester/js/examples/SegmentedControlIOS/SegmentedControlExampleComponents.js b/RNTester/js/examples/SegmentedControlIOS/SegmentedControlExampleComponents.js new file mode 100644 index 00000000000000..8b06f064fda39f --- /dev/null +++ b/RNTester/js/examples/SegmentedControlIOS/SegmentedControlExampleComponents.js @@ -0,0 +1,114 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +'use strict'; + +import * as React from 'react'; +import {SegmentedControlIOS, Text, View, StyleSheet} from 'react-native'; + +export function BasicSegmentedControlExample(): React.Node { + return ( + + + + + + + + + ); +} + +export function PreSelectedSegmentedControlExample(): React.Node { + return ( + + + + + + ); +} + +export function MomentarySegmentedControlExample(): React.Node { + return ( + + + + + + ); +} + +export function DisabledSegmentedControlExample(): React.Node { + return ( + + + + + + ); +} + +export function ColorSegmentedControlExample(): React.Node { + return ( + + + + + + + + + ); +} + +export function EventSegmentedControlExample(): React.Node { + const [selectedIndex, setSelectedIndex] = React.useState(undefined); + const [value, setValue] = React.useState('Not selected'); + const values = ['One', 'Two', 'Three']; + + return ( + + Value: {value} + Index: {selectedIndex} + { + setSelectedIndex(event.nativeEvent.selectedSegmentIndex); + }} + onValueChange={changedValue => { + setValue(changedValue); + }} + /> + + ); +} + +const styles = StyleSheet.create({ + text: { + fontSize: 14, + textAlign: 'center', + fontWeight: '500', + margin: 10, + }, +}); diff --git a/RNTester/js/examples/SegmentedControlIOS/SegmentedControlIOSExample.js b/RNTester/js/examples/SegmentedControlIOS/SegmentedControlIOSExample.js index 0c20da7b148ef6..2824475b838f6a 100644 --- a/RNTester/js/examples/SegmentedControlIOS/SegmentedControlIOSExample.js +++ b/RNTester/js/examples/SegmentedControlIOS/SegmentedControlIOSExample.js @@ -11,134 +11,14 @@ 'use strict'; const React = require('react'); -const {SegmentedControlIOS, Text, View, StyleSheet} = require('react-native'); - -class BasicSegmentedControlExample extends React.Component<{...}> { - render() { - return ( - - - - - - - - - ); - } -} - -class PreSelectedSegmentedControlExample extends React.Component<{...}> { - render() { - return ( - - - - - - ); - } -} - -class MomentarySegmentedControlExample extends React.Component<{...}> { - render() { - return ( - - - - - - ); - } -} - -class DisabledSegmentedControlExample extends React.Component<{...}> { - render() { - return ( - - - - - - ); - } -} - -class ColorSegmentedControlExample extends React.Component<{...}> { - render() { - return ( - - - - - - - - - ); - } -} - -class EventSegmentedControlExample extends React.Component< - {...}, - $FlowFixMeState, -> { - state = { - values: ['One', 'Two', 'Three'], - value: 'Not selected', - selectedIndex: undefined, - }; - - render() { - return ( - - Value: {this.state.value} - Index: {this.state.selectedIndex} - - - ); - } - - _onChange = event => { - this.setState({ - selectedIndex: event.nativeEvent.selectedSegmentIndex, - }); - }; - - _onValueChange = value => { - this.setState({ - value: value, - }); - }; -} - -const styles = StyleSheet.create({ - text: { - fontSize: 14, - textAlign: 'center', - fontWeight: '500', - margin: 10, - }, -}); +const { + BasicSegmentedControlExample, + PreSelectedSegmentedControlExample, + MomentarySegmentedControlExample, + DisabledSegmentedControlExample, + ColorSegmentedControlExample, + EventSegmentedControlExample, +} = require('./SegmentedControlExampleComponents'); exports.title = ''; exports.displayName = 'SegmentedControlExample'; diff --git a/RNTester/js/examples/Text/TextExample.android.js b/RNTester/js/examples/Text/TextExample.android.js index 72813a71cd97aa..f9631821f9900f 100644 --- a/RNTester/js/examples/Text/TextExample.android.js +++ b/RNTester/js/examples/Text/TextExample.android.js @@ -664,6 +664,9 @@ class TextExample extends React.Component<{...}> { + + + diff --git a/RNTester/js/examples/TextInput/TextInputExample.ios.js b/RNTester/js/examples/TextInput/TextInputExample.ios.js index 7e1673070e0109..311efa1179b066 100644 --- a/RNTester/js/examples/TextInput/TextInputExample.ios.js +++ b/RNTester/js/examples/TextInput/TextInputExample.ios.js @@ -707,4 +707,16 @@ exports.examples = ([ ); }, }, + { + title: 'showSoftInputOnFocus', + render: function(): React.Node { + return ( + + + + + + ); + }, + }, ]: Array); diff --git a/React-Core.podspec b/React-Core.podspec index 9acf10cbebf3fd..fc0a69f377561b 100644 --- a/React-Core.podspec +++ b/React-Core.podspec @@ -122,6 +122,7 @@ Pod::Spec.new do |s| s.dependency "RCT-Folly", folly_version s.dependency "React-cxxreact", version + s.dependency "React-perflogger", version s.dependency "React-jsi", version s.dependency "React-jsiexecutor", version s.dependency "Yoga" diff --git a/React/Base/RCTBundleURLProvider.h b/React/Base/RCTBundleURLProvider.h index 4254c9cae9b96e..cb9dc6f88a655e 100644 --- a/React/Base/RCTBundleURLProvider.h +++ b/React/Base/RCTBundleURLProvider.h @@ -39,9 +39,7 @@ extern NSString *const kRCTPlatformName; // TODO(macOS GH#774) */ - (NSString *)packagerServerHost; -#if RCT_DEV_MENU + (BOOL)isPackagerRunning:(NSString *)host; -#endif /** * Returns the jsBundleURL for a given bundle entrypoint and diff --git a/React/Base/RCTBundleURLProvider.m b/React/Base/RCTBundleURLProvider.m index e50b08fafc7a2d..c4a0684d4d41be 100644 --- a/React/Base/RCTBundleURLProvider.m +++ b/React/Base/RCTBundleURLProvider.m @@ -116,6 +116,11 @@ - (NSString *)guessPackagerHost } return nil; } +#else ++ (BOOL)isPackagerRunning:(NSString *)host +{ + return false; +} #endif - (NSString *)packagerServerHost @@ -194,6 +199,10 @@ + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot kRCTPlatformName, // TODO(macOS GH#774) enableDev ? @"true" : @"false", enableMinification ? @"true" : @"false"]; + NSString *bundleID = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey]; + if (bundleID) { + query = [NSString stringWithFormat:@"%@&app=%@", query, bundleID]; + } return [[self class] resourceURLForResourcePath:path packagerHost:packagerHost query:query]; } diff --git a/React/Base/RCTJSInvokerModule.h b/React/Base/RCTJSInvokerModule.h index 4932c4703cabed..018c9b76f9508d 100644 --- a/React/Base/RCTJSInvokerModule.h +++ b/React/Base/RCTJSInvokerModule.h @@ -6,7 +6,7 @@ */ /** - * This protocol should be adopted when a turbo module needs to directly call into Javascript. + * This protocol should be adopted when a turbo module needs to directly call into JavaScript. * In bridge-less React Native, it is a replacement for [_bridge enqueueJSCall:]. */ @protocol RCTJSInvokerModule diff --git a/React/Base/RCTKeyCommands.h b/React/Base/RCTKeyCommands.h index 983348e9989212..2bdefcfab9dbed 100644 --- a/React/Base/RCTKeyCommands.h +++ b/React/Base/RCTKeyCommands.h @@ -12,37 +12,20 @@ + (instancetype)sharedInstance; /** - * Register a single-press keyboard command. + * Register a keyboard command. */ - (void)registerKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags action:(void (^)(UIKeyCommand *command))block; /** - * Unregister a single-press keyboard command. + * Unregister a keyboard command. */ - (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags; /** - * Check if a single-press command is registered. + * Check if a command is registered. */ - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags; -/** - * Register a double-press keyboard command. - */ -- (void)registerDoublePressKeyCommandWithInput:(NSString *)input - modifierFlags:(UIKeyModifierFlags)flags - action:(void (^)(UIKeyCommand *command))block; - -/** - * Unregister a double-press keyboard command. - */ -- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags; - -/** - * Check if a double-press command is registered. - */ -- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags; - @end diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index d48ba935d22f19..220a8b321f09cf 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -9,24 +9,38 @@ #import +#import +#import #import "RCTDefines.h" #import "RCTUtils.h" #if RCT_DEV +@interface UIEvent (UIPhysicalKeyboardEvent) + +@property (nonatomic) NSString *_modifiedInput; +@property (nonatomic) NSString *_unmodifiedInput; +@property (nonatomic) UIKeyModifierFlags _modifierFlags; +@property (nonatomic) BOOL _isKeyDown; +@property (nonatomic) long _keyCode; + +@end + @interface RCTKeyCommand : NSObject -@property (nonatomic, strong) UIKeyCommand *keyCommand; +@property (nonatomic, copy, readonly) NSString *key; +@property (nonatomic, readonly) UIKeyModifierFlags flags; @property (nonatomic, copy) void (^block)(UIKeyCommand *); @end @implementation RCTKeyCommand -- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand block:(void (^)(UIKeyCommand *))block +- (instancetype)init:(NSString *)key flags:(UIKeyModifierFlags)flags block:(void (^)(UIKeyCommand *))block { if ((self = [super init])) { - _keyCommand = keyCommand; + _key = key; + _flags = flags; _block = block; } return self; @@ -41,7 +55,7 @@ - (id)copyWithZone:(__unused NSZone *)zone - (NSUInteger)hash { - return _keyCommand.input.hash ^ _keyCommand.modifierFlags; + return _key.hash ^ _flags; } - (BOOL)isEqual:(RCTKeyCommand *)object @@ -49,12 +63,15 @@ - (BOOL)isEqual:(RCTKeyCommand *)object if (![object isKindOfClass:[RCTKeyCommand class]]) { return NO; } - return [self matchesInput:object.keyCommand.input flags:object.keyCommand.modifierFlags]; + return [self matchesInput:object.key flags:object.flags]; } - (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags { - return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags; + // We consider the key command a match if the modifier flags match + // exactly or is there are no modifier flags. This means that for + // `cmd + r`, we will match both `cmd + r` and `r` but not `opt + r`. + return [_key isEqual:input] && (_flags == flags || flags == 0); } - (NSString *)description @@ -62,8 +79,8 @@ - (NSString *)description return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%lld hasBlock=%@>", [self class], self, - _keyCommand.input, - (long long)_keyCommand.modifierFlags, + _key, + (long long)_flags, _block ? @"YES" : @"NO"]; } @@ -75,115 +92,94 @@ @interface RCTKeyCommands () @end -@implementation UIResponder (RCTKeyCommands) +@implementation RCTKeyCommands -+ (UIResponder *)RCT_getFirstResponder:(UIResponder *)view ++ (void)initialize { - UIResponder *firstResponder = nil; + SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:"); + SEL swizzledKeyEventSelector = NSSelectorFromString( + [NSString stringWithFormat:@"_rct_swizzle_%x_%@", arc4random(), NSStringFromSelector(originalKeyEventSelector)]); - if (view.isFirstResponder) { - return view; - } else if ([view isKindOfClass:[UIViewController class]]) { - if ([(UIViewController *)view parentViewController]) { - firstResponder = [UIResponder RCT_getFirstResponder:[(UIViewController *)view parentViewController]]; - } - return firstResponder ? firstResponder : [UIResponder RCT_getFirstResponder:[(UIViewController *)view view]]; - } else if ([view isKindOfClass:[UIView class]]) { - for (UIView *subview in [(UIView *)view subviews]) { - firstResponder = [UIResponder RCT_getFirstResponder:subview]; - if (firstResponder) { - return firstResponder; - } - } - } + void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) { + [[[self class] sharedInstance] handleKeyUIEventSwizzle:event]; - return firstResponder; -} + ((void (*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event); + }; -- (NSArray *)RCT_keyCommands -{ - NSSet *commands = [RCTKeyCommands sharedInstance].commands; - return [[commands valueForKeyPath:@"keyCommand"] allObjects]; + RCTSwapInstanceMethodWithBlock( + [UIApplication class], originalKeyEventSelector, handleKeyUIEventSwizzleBlock, swizzledKeyEventSelector); } -/** - * Single Press Key Command Response - * Command + KeyEvent (Command + R/D, etc.) - */ -- (void)RCT_handleKeyCommand:(UIKeyCommand *)key +- (void)handleKeyUIEventSwizzle:(UIEvent *)event { - // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand: - // method gets called repeatedly if the command key is held down. - static NSTimeInterval lastCommand = 0; - if (CACurrentMediaTime() - lastCommand > 0.5) { - for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { - if ([command.keyCommand.input isEqualToString:key.input] && - command.keyCommand.modifierFlags == key.modifierFlags) { - if (command.block) { - command.block(key); - lastCommand = CACurrentMediaTime(); - } - } - } + NSString *modifiedInput = nil; + UIKeyModifierFlags *modifierFlags = nil; + BOOL isKeyDown = NO; + + if ([event respondsToSelector:@selector(_modifiedInput)]) { + modifiedInput = [event _modifiedInput]; } -} -/** - * Double Press Key Command Response - * Double KeyEvent (Double R, etc.) - */ -- (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key -{ - static BOOL firstPress = YES; - static NSTimeInterval lastCommand = 0; - static NSTimeInterval lastDoubleCommand = 0; - static NSString *lastInput = nil; - static UIKeyModifierFlags lastModifierFlags = 0; - - if (firstPress) { - for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { - if ([command.keyCommand.input isEqualToString:key.input] && - command.keyCommand.modifierFlags == key.modifierFlags && command.block) { - firstPress = NO; - lastCommand = CACurrentMediaTime(); - lastInput = key.input; - lastModifierFlags = key.modifierFlags; - return; - } - } - } else { - // Second keyevent within 0.2 second, - // with the same key as the first one. - if (CACurrentMediaTime() - lastCommand < 0.2 && lastInput == key.input && lastModifierFlags == key.modifierFlags) { - for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { - if ([command.keyCommand.input isEqualToString:key.input] && - command.keyCommand.modifierFlags == key.modifierFlags && command.block) { - // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand: - // method gets called repeatedly if the command key is held down. - if (CACurrentMediaTime() - lastDoubleCommand > 0.5) { - command.block(key); - lastDoubleCommand = CACurrentMediaTime(); - } - firstPress = YES; - return; - } + if ([event respondsToSelector:@selector(_modifierFlags)]) { + modifierFlags = [event _modifierFlags]; + } + + if ([event respondsToSelector:@selector(_isKeyDown)]) { + isKeyDown = [event _isKeyDown]; + } + + BOOL interactionEnabled = !UIApplication.sharedApplication.isIgnoringInteractionEvents; + BOOL hasFirstResponder = NO; + if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) { + UIResponder *firstResponder = nil; + for (UIWindow *window in [self allWindows]) { + firstResponder = [window valueForKey:@"firstResponder"]; + if (firstResponder) { + hasFirstResponder = YES; + break; } } - lastCommand = CACurrentMediaTime(); - lastInput = key.input; - lastModifierFlags = key.modifierFlags; + // Ignore key commands (except escape) when there's an active responder + if (!firstResponder) { + [self RCT_handleKeyCommand:modifiedInput flags:modifierFlags]; + } } -} - -@end +}; -@implementation RCTKeyCommands +- (NSArray *)allWindows +{ + BOOL includeInternalWindows = YES; + BOOL onlyVisibleWindows = NO; + + // Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows: + NSArray *allWindowsComponents = + @[ @"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:" ]; + SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]); + + NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + + invocation.target = [UIWindow class]; + invocation.selector = allWindowsSelector; + [invocation setArgument:&includeInternalWindows atIndex:2]; + [invocation setArgument:&onlyVisibleWindows atIndex:3]; + [invocation invoke]; + + __unsafe_unretained NSArray *windows = nil; + [invocation getReturnValue:&windows]; + return windows; +} -+ (void)initialize +- (void)RCT_handleKeyCommand:(NSString *)input flags:(UIKeyModifierFlags)modifierFlags { - // swizzle UIResponder - RCTSwapInstanceMethods([UIResponder class], @selector(keyCommands), @selector(RCT_keyCommands)); + for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { + if ([command matchesInput:input flags:modifierFlags]) { + if (command.block) { + command.block(nil); + } + } + } } + (instancetype)sharedInstance @@ -211,11 +207,7 @@ - (void)registerKeyCommandWithInput:(NSString *)input { RCTAssertMainQueue(); - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input - modifierFlags:flags - action:@selector(RCT_handleKeyCommand:)]; - - RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block]; + RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] init:input flags:flags block:block]; [_commands removeObject:keyCommand]; [_commands addObject:keyCommand]; } @@ -244,45 +236,6 @@ - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyMod return NO; } -- (void)registerDoublePressKeyCommandWithInput:(NSString *)input - modifierFlags:(UIKeyModifierFlags)flags - action:(void (^)(UIKeyCommand *))block -{ - RCTAssertMainQueue(); - - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input - modifierFlags:flags - action:@selector(RCT_handleDoublePressKeyCommand:)]; - - RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block]; - [_commands removeObject:keyCommand]; - [_commands addObject:keyCommand]; -} - -- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags -{ - RCTAssertMainQueue(); - - for (RCTKeyCommand *command in _commands.allObjects) { - if ([command matchesInput:input flags:flags]) { - [_commands removeObject:command]; - break; - } - } -} - -- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags -{ - RCTAssertMainQueue(); - - for (RCTKeyCommand *command in _commands) { - if ([command matchesInput:input flags:flags]) { - return YES; - } - } - return NO; -} - @end #else @@ -309,21 +262,6 @@ - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyMod return NO; } -- (void)registerDoublePressKeyCommandWithInput:(NSString *)input - modifierFlags:(UIKeyModifierFlags)flags - action:(void (^)(UIKeyCommand *))block -{ -} - -- (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags -{ -} - -- (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags -{ - return NO; -} - @end #endif diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm index b4e48ae5e121bd..14251ee6d590e6 100644 --- a/React/Base/RCTModuleData.mm +++ b/React/Base/RCTModuleData.mm @@ -8,7 +8,10 @@ #import "RCTModuleData.h" #import -#include +#import +#import + +#import #import "RCTBridge+Private.h" #import "RCTBridge.h" @@ -17,6 +20,16 @@ #import "RCTProfile.h" #import "RCTUtils.h" +using namespace facebook::react; + +namespace { +int32_t getUniqueId() +{ + static std::atomic counter{0}; + return counter++; +} +} + @implementation RCTModuleData { NSDictionary *_constantsToExport; NSString *_queueName; @@ -110,22 +123,29 @@ - (instancetype)initWithModuleInstance:(id)instance bridge:(RCT #pragma mark - private setup methods -- (void)setUpInstanceAndBridge +- (void)setUpInstanceAndBridge:(int32_t)requestId { + NSString *moduleName = [self name]; + RCT_PROFILE_BEGIN_EVENT( RCTProfileTagAlways, @"[RCTModuleData setUpInstanceAndBridge]", @{@"moduleClass" : NSStringFromClass(_moduleClass)}); { std::unique_lock lock(_instanceLock); + BOOL shouldSetup = !_setupComplete && _bridge.valid; - if (!_setupComplete && _bridge.valid) { + if (shouldSetup) { if (!_instance) { if (RCT_DEBUG && _requiresMainQueueSetup) { RCTAssertMainQueue(); } RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setUpInstanceAndBridge] Create module", nil); + + BridgeNativeModulePerfLogger::moduleCreateConstructStart([moduleName UTF8String], requestId); _instance = _moduleProvider ? _moduleProvider() : nil; + BridgeNativeModulePerfLogger::moduleCreateConstructEnd([moduleName UTF8String], requestId); + RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); if (!_instance) { // Module init returned nil, probably because automatic instantiation @@ -143,7 +163,13 @@ - (void)setUpInstanceAndBridge if (_instance && RCTProfileIsProfiling()) { RCTProfileHookInstance(_instance); } + } + + if (_instance) { + BridgeNativeModulePerfLogger::moduleCreateSetUpStart([moduleName UTF8String], requestId); + } + if (shouldSetup) { // Bridge must be set before methodQueue is set up, as methodQueue // initialization requires it (View Managers get their queue by calling // self.bridge.uiManager.methodQueue) @@ -171,6 +197,10 @@ - (void)setUpInstanceAndBridge // thread. _requiresMainQueueSetup = NO; } + + if (_instance) { + BridgeNativeModulePerfLogger::moduleCreateSetUpEnd([moduleName UTF8String], requestId); + } } - (void)setBridgeForInstance @@ -286,6 +316,10 @@ - (BOOL)hasInstance - (id)instance { + NSString *moduleName = [self name]; + int32_t requestId = getUniqueId(); + BridgeNativeModulePerfLogger::moduleCreateStart([moduleName UTF8String], requestId); + if (!_setupComplete) { RCT_PROFILE_BEGIN_EVENT( RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData instanceForClass:%@]", _moduleClass]), nil); @@ -301,13 +335,21 @@ - (BOOL)hasInstance } RCTUnsafeExecuteOnMainQueueSync(^{ - [self setUpInstanceAndBridge]; + [self setUpInstanceAndBridge:requestId]; }); RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } else { - [self setUpInstanceAndBridge]; + [self setUpInstanceAndBridge:requestId]; } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); + } else { + BridgeNativeModulePerfLogger::moduleCreateCacheHit([moduleName UTF8String], requestId); + } + + if (_instance) { + BridgeNativeModulePerfLogger::moduleCreateEnd([moduleName UTF8String], requestId); + } else { + BridgeNativeModulePerfLogger::moduleCreateFail([moduleName UTF8String], requestId); } return _instance; } @@ -331,10 +373,22 @@ - (NSString *)name - (void)gatherConstants { + NSString *moduleName = [self name]; + if (_hasConstantsToExport && !_constantsToExport) { RCT_PROFILE_BEGIN_EVENT( RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil); (void)[self instance]; + + /** + * Why do we instrument moduleJSRequireEndingStart here? + * - NativeModule requires from JS go through ModuleRegistry::getConfig(). + * - ModuleRegistry::getConfig() calls NativeModule::getConstants() first. + * - This delegates to RCTNativeModule::getConstants(), which calls RCTModuleData gatherConstants(). + * - Therefore, this is the first statement that executes after the NativeModule is created/initialized in a JS + * require. + */ + BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]); if (_requiresMainQueueSetup) { if (!RCTIsMainQueue()) { RCTLogWarn(@"Required dispatch_sync to load constants for %@. This may lead to deadlocks", _moduleClass); @@ -347,6 +401,12 @@ - (void)gatherConstants _constantsToExport = [_instance constantsToExport] ?: @{}; } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); + } else { + /** + * If a NativeModule doesn't have constants, it isn't eagerly loaded until its methods are first invoked. + * Therefore, we should immediately start JSRequireEnding + */ + BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]); } } diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index e69f8f2c5f8907..2595fbcf6659f9 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -61,6 +61,7 @@ RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale); // Method swizzling RCT_EXTERN IMP RCTSwapClassMethods(Class cls, SEL original, SEL replacement); // TODO(OSS Candidate ISS#2710739) RCT_EXTERN IMP RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement); // TODO(OSS Candidate ISS#2710739) +RCT_EXTERN void RCTSwapInstanceMethodWithBlock(Class cls, SEL original, id replacementBlock, SEL replacementSelector); // Module subclass support RCT_EXTERN BOOL RCTClassOverridesClassMethod(Class cls, SEL selector); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 953bed5855c28b..beee44e90ed135 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -404,6 +404,19 @@ IMP RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement) // TODO(OSS return originalImplementation; // TODO(OSS Candidate ISS#2710739) } +void RCTSwapInstanceMethodWithBlock(Class cls, SEL original, id replacementBlock, SEL replacementSelector) +{ + Method originalMethod = class_getInstanceMethod(cls, original); + if (!originalMethod) { + return; + } + + IMP implementation = imp_implementationWithBlock(replacementBlock); + class_addMethod(cls, replacementSelector, implementation, method_getTypeEncoding(originalMethod)); + Method newMethod = class_getInstanceMethod(cls, replacementSelector); + method_exchangeImplementations(originalMethod, newMethod); +} + BOOL RCTClassOverridesClassMethod(Class cls, SEL selector) { return RCTClassOverridesInstanceMethod(object_getClass(cls), selector); diff --git a/React/Base/RCTUtilsUIOverride.h b/React/Base/RCTUtilsUIOverride.h index d89dcbc14854e8..1ac2bc17c01311 100644 --- a/React/Base/RCTUtilsUIOverride.h +++ b/React/Base/RCTUtilsUIOverride.h @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +#import +#import // TODO(macOS GH#774) + @interface RCTUtilsUIOverride : NSObject /** Set the global presented view controller instance override. diff --git a/React/CoreModules/BUCK b/React/CoreModules/BUCK index 155d6378fa3c09..274a89b115fdd6 100644 --- a/React/CoreModules/BUCK +++ b/React/CoreModules/BUCK @@ -17,6 +17,7 @@ rn_apple_library( compiler_flags = [ "-Wno-error=unguarded-availability-new", "-Wno-unknown-warning-option", + "-Wno-unused-property-ivar", ], contacts = ["oncall+react_native@xmail.facebook.com"], enable_exceptions = True, @@ -35,7 +36,7 @@ rn_apple_library( header_path_prefix = "React", labels = [ "depslint_never_remove", # Some old NativeModule still relies on +load unfortunately. - "supermodule:ios/default/public.react_native.infra", + "supermodule:xplat/default/public.react_native.infra", ], link_whole = True, platform_preprocessor_flags = [( diff --git a/React/CoreModules/RCTAppearance.h b/React/CoreModules/RCTAppearance.h index 1f91bbdf061eb8..03da22857ff469 100644 --- a/React/CoreModules/RCTAppearance.h +++ b/React/CoreModules/RCTAppearance.h @@ -12,6 +12,11 @@ RCT_EXTERN void RCTEnableAppearancePreference(BOOL enabled); RCT_EXTERN void RCTOverrideAppearancePreference(NSString *const); +#if !TARGET_OS_OSX // TODO(macOS GH#774) +RCT_EXTERN NSString *RCTColorSchemePreference(UITraitCollection *traitCollection); +#else // [TODO(macOS GH#774) +RCT_EXTERN NSString *RCTColorSchemePreference(NSAppearance *appearance); +#endif // ]TODO(macOS GH#774) @interface RCTAppearance : RCTEventEmitter @end diff --git a/React/CoreModules/RCTAppearance.mm b/React/CoreModules/RCTAppearance.mm index 36525f9563da39..b0da18b72db345 100644 --- a/React/CoreModules/RCTAppearance.mm +++ b/React/CoreModules/RCTAppearance.mm @@ -31,7 +31,7 @@ void RCTOverrideAppearancePreference(NSString *const colorSchemeOverride) } #if !TARGET_OS_OSX // TODO(macOS GH#774) -static NSString *RCTColorSchemePreference(UITraitCollection *traitCollection) +NSString *RCTColorSchemePreference(UITraitCollection *traitCollection) { #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 @@ -64,7 +64,7 @@ void RCTOverrideAppearancePreference(NSString *const colorSchemeOverride) return RCTAppearanceColorSchemeLight; } #else // [TODO(macOS GH#774) -static NSString *RCTColorSchemePreference(NSAppearance *appearance) +NSString *RCTColorSchemePreference(NSAppearance *appearance) { if (@available(macOS 10.14, *)) { static NSDictionary *appearances; diff --git a/React/CoreModules/RCTDevLoadingView.mm b/React/CoreModules/RCTDevLoadingView.mm index da8c928f315da1..0a899170c080bd 100644 --- a/React/CoreModules/RCTDevLoadingView.mm +++ b/React/CoreModules/RCTDevLoadingView.mm @@ -10,6 +10,7 @@ #import #import +#import #import #import #import @@ -243,27 +244,65 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo }); } +- (void)showProgressMessage:(NSString *)message +{ + if (self->_window != nil) { + // This is an optimization. Since the progress can come in quickly, + // we want to do the minimum amount of work to update the UI, + // which is to only update the label text. +#if !TARGET_OS_OSX // TODO(macOS GH#774) + self->_label.text = message; +#else // [TODO(macOS GH#774) + self->_label.stringValue = message; +#endif // ]TODO(macOS GH#774) + return; + } + + RCTUIColor *color = [RCTUIColor whiteColor]; // TODO(macOS GH#774) + RCTUIColor *backgroundColor = [RCTUIColor colorWithHue:105 saturation:0 brightness:.25 alpha:1]; // TODO(macOS GH#774) + + if ([self isDarkModeEnabled]) { + color = [RCTUIColor colorWithHue:208 saturation:0.03 brightness:.14 alpha:1]; // TODO(macOS GH#774) + backgroundColor = [RCTUIColor colorWithHue:0 saturation:0 brightness:0.98 alpha:1]; // TODO(macOS GH#774) + } + + [self showMessage:message color:color backgroundColor:backgroundColor]; +} + +- (void)showOfflineMessage +{ + RCTUIColor *color = [RCTUIColor whiteColor]; // TODO(macOS GH#774) + RCTUIColor *backgroundColor = [RCTUIColor blackColor]; // TODO(macOS GH#774) + + if ([self isDarkModeEnabled]) { + color = [RCTUIColor blackColor]; // TODO(macOS GH#774) + backgroundColor = [RCTUIColor whiteColor]; // TODO(macOS GH#774) + } + + NSString *message = [NSString stringWithFormat:@"Connect to %@ to develop JavaScript.", RCT_PACKAGER_NAME]; + [self showMessage:message color:color backgroundColor:backgroundColor]; +} + +- (BOOL)isDarkModeEnabled +{ + // We pass nil here to match the behavior of the native module. + // If we were to pass a view, then it's possible that this native + // banner would have a different color than the JavaScript banner + // (which always passes nil). This would result in an inconsistent UI. + return [RCTColorSchemePreference(nil) isEqualToString:@"dark"]; +} - (void)showWithURL:(NSURL *)URL { - RCTUIColor *color; // TODO(macOS GH#774) - RCTUIColor *backgroundColor; // TODO(macOS GH#774) - NSString *message; if (URL.fileURL) { // If dev mode is not enabled, we don't want to show this kind of notification. #if !RCT_DEV return; #endif - color = [RCTUIColor whiteColor]; //TODO(OSS Candidate ISS#2710739) UIColor -> RCTUIColor - backgroundColor = [RCTUIColor blackColor]; // TODO(OSS Candidate ISS#2710739) - message = [NSString stringWithFormat:@"Connect to %@ to develop JavaScript.", RCT_PACKAGER_NAME]; - [self showMessage:message color:color backgroundColor:backgroundColor]; + [self showOfflineMessage]; } else { - color = [RCTUIColor whiteColor]; // TODO(OSS Candidate ISS#2710739) - backgroundColor = [RCTUIColor colorWithHue:105 saturation:0 brightness:.25 alpha:1]; // TODO(OSS Candidate ISS#2710739) - message = [NSString stringWithFormat:@"Loading from %@\u2026", RCT_PACKAGER_NAME]; - [self showInitialMessageDelayed:^{ - [self showMessage:message color:color backgroundColor:backgroundColor]; + NSString *message = [NSString stringWithFormat:@"Loading from %@\u2026", RCT_PACKAGER_NAME]; + [self showProgressMessage:message]; }]; } } @@ -278,22 +317,7 @@ - (void)updateProgress:(RCTLoadingProgress *)progress [self clearInitialMessageDelay]; dispatch_async(dispatch_get_main_queue(), ^{ - if (self->_window == nil) { - // If we didn't show the initial message, then there's no banner window. - // We need to create it here so that the progress is actually displayed. - RCTUIColor *color = [RCTUIColor whiteColor]; - RCTUIColor *backgroundColor = [RCTUIColor colorWithHue:105 saturation:0 brightness:.25 alpha:1]; - [self showMessage:[progress description] color:color backgroundColor:backgroundColor]; - } else { - // This is an optimization. Since the progress can come in quickly, - // we want to do the minimum amount of work to update the UI, - // which is to only update the label text. -#if !TARGET_OS_OSX // TODO(macOS GH#774) - self->_label.text = [progress description]; -#else // [TODO(macOS GH#774) - self->_label.stringValue = [progress description]; -#endif // ]TODO(macOS GH#774) - } + [self showProgressMessage:[progress description]]; }); } diff --git a/React/CoreModules/RCTExceptionsManager.mm b/React/CoreModules/RCTExceptionsManager.mm index 4899d7af7cd78f..072e4511f2d813 100644 --- a/React/CoreModules/RCTExceptionsManager.mm +++ b/React/CoreModules/RCTExceptionsManager.mm @@ -24,6 +24,7 @@ @interface RCTExceptionsManager () @implementation RCTExceptionsManager @synthesize bridge = _bridge; +@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate; RCT_EXPORT_MODULE() @@ -41,7 +42,13 @@ - (void)reportSoft:(NSString *)message suppressRedBox:(BOOL)suppressRedBox { if (!suppressRedBox) { - [_bridge.redBox showErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; + // TODO T5287269 - Delete _bridge case when TM ships. + if (_bridge) { + [_bridge.redBox showErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; + } else { + RCTRedBox *redbox = [_turboModuleLookupDelegate moduleForName:"RCTRedBox"]; + [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; + } } if (_delegate) { @@ -57,7 +64,13 @@ - (void)reportFatal:(NSString *)message suppressRedBox:(BOOL)suppressRedBox { if (!suppressRedBox) { - [_bridge.redBox showErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; + // TODO T5287269 - Delete _bridge case when TM ships. + if (_bridge) { + [_bridge.redBox showErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; + } else { + RCTRedBox *redbox = [_turboModuleLookupDelegate moduleForName:"RCTRedBox"]; + [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; + } } if (_delegate) { @@ -98,7 +111,13 @@ - (void)reportFatal:(NSString *)message : (NSArray *)stack exceptionId : (double)exceptionId) { - [_bridge.redBox updateErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; + // TODO T5287269 - Delete _bridge case when TM ships. + if (_bridge) { + [_bridge.redBox updateErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; + } else { + RCTRedBox *redbox = [_turboModuleLookupDelegate moduleForName:"RCTRedBox"]; + [redbox updateErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; + } if (_delegate && [_delegate respondsToSelector:@selector(updateJSExceptionWithMessage:stack:exceptionId:)]) { [_delegate updateJSExceptionWithMessage:message stack:stack exceptionId:[NSNumber numberWithDouble:exceptionId]]; diff --git a/React/CoreModules/RCTLogBox.h b/React/CoreModules/RCTLogBox.h index 3aaa8c97ee04f0..f81805d2549ffd 100644 --- a/React/CoreModules/RCTLogBox.h +++ b/React/CoreModules/RCTLogBox.h @@ -7,15 +7,6 @@ #import // TODO(macOS GH#774) -#import -#import -#import - -@class RCTJSStackFrame; - -@interface RCTLogBox : NSObject - -- (void)show; -- (void)hide; +@interface RCTLogBox : NSObject @end diff --git a/React/CoreModules/RCTLogBox.mm b/React/CoreModules/RCTLogBox.mm index 1693cc6fb31c95..ebb57011052995 100644 --- a/React/CoreModules/RCTLogBox.mm +++ b/React/CoreModules/RCTLogBox.mm @@ -9,18 +9,10 @@ #import #import -#import -#import -#import -#import -#import +#import +#import #import -#import -#import #import -#import - -#import #import "CoreModulesPlugins.h" @@ -139,7 +131,7 @@ - (void)_showModal #endif // ]TODO(macOS GH#774) -@interface RCTLogBox () +@interface RCTLogBox () @end @implementation RCTLogBox { diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index a996fda0b6fab9..b2501d62d64791 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -36,6 +36,7 @@ #import #import #import +#import #if TARGET_OS_OSX && __has_include() #define RCT_USE_HERMES 1 @@ -78,6 +79,12 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { namespace { +int32_t getUniqueId() +{ + static std::atomic counter{0}; + return counter++; +} + class GetDescAdapter : public JSExecutorFactory { public: GetDescAdapter(RCTCxxBridge *bridge, std::shared_ptr factory) : bridge_(bridge), factory_(factory) @@ -258,24 +265,10 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge _moduleDataByID = [NSMutableArray new]; [RCTBridge setCurrentBridge:self]; - -#if !TARGET_OS_OSX // TODO(macOS GH#774) - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleMemoryWarning) - name:UIApplicationDidReceiveMemoryWarningNotification - object:nil]; -#endif // TODO(macOS GH#774) } return self; } -#if !TARGET_OS_OSX // TODO(macOS GH#774) -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} -#endif // TODO(macOS GH#774) - + (void)runRunLoop { @autoreleasepool { @@ -309,13 +302,6 @@ - (void)_tryAndHandleError:(dispatch_block_t)block } } -- (void)handleMemoryWarning -{ - if (_reactInstance) { - _reactInstance->handleMemoryPressure(15 /* TRIM_MEMORY_RUNNING_CRITICAL */); - } -} - /** * Ensure block is run on the JS thread. If we're already on the JS thread, the block will execute synchronously. * If we're not on the JS thread, the block is dispatched to that thread. Any errors encountered while executing @@ -702,7 +688,10 @@ - (void)updateModuleWithInstance:(id)instance // Instantiate moduleData // TODO #13258411: can we defer this until config generation? + int32_t moduleDataId = getUniqueId(); + BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId); moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; + BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); _moduleDataByName[moduleName] = moduleData; [_moduleClassesByID addObject:moduleClass]; @@ -762,7 +751,11 @@ - (void)registerExtraModules } // Instantiate moduleData container + int32_t moduleDataId = getUniqueId(); + BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId); RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module bridge:self]; + BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); + _moduleDataByName[moduleName] = moduleData; [_moduleClassesByID addObject:moduleClass]; [_moduleDataByID addObject:moduleData]; @@ -809,7 +802,10 @@ - (void)registerExtraLazyModules } } + int32_t moduleDataId = getUniqueId(); + BridgeNativeModulePerfLogger::moduleDataCreateStart([moduleName UTF8String], moduleDataId); moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; + BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); _moduleDataByName[moduleName] = moduleData; [_moduleClassesByID addObject:moduleClass]; diff --git a/React/CxxModule/RCTNativeModule.h b/React/CxxModule/RCTNativeModule.h index c73cf5f4ab7e8d..a59f9ae48181a9 100644 --- a/React/CxxModule/RCTNativeModule.h +++ b/React/CxxModule/RCTNativeModule.h @@ -16,6 +16,7 @@ class RCTNativeModule : public NativeModule { RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData); std::string getName() override; + std::string getSyncMethodName(unsigned int methodId) override; std::vector getMethods() override; folly::dynamic getConstants() override; void invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) diff --git a/React/CxxModule/RCTNativeModule.mm b/React/CxxModule/RCTNativeModule.mm index 00737371518d77..be74794fc572c0 100644 --- a/React/CxxModule/RCTNativeModule.mm +++ b/React/CxxModule/RCTNativeModule.mm @@ -15,16 +15,26 @@ #import #import #import +#import #ifdef WITH_FBSYSTRACE #include #endif +namespace { +enum SchedulingContext { Sync, Async }; +} + namespace facebook { namespace react { -static MethodCallResult -invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic ¶ms); +static MethodCallResult invokeInner( + RCTBridge *bridge, + RCTModuleData *moduleData, + unsigned int methodId, + const folly::dynamic ¶ms, + int callId, + SchedulingContext context); RCTNativeModule::RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData) : m_bridge(bridge), m_moduleData(moduleData) @@ -36,6 +46,11 @@ return [m_moduleData.name UTF8String]; } +std::string RCTNativeModule::getSyncMethodName(unsigned int methodId) +{ + return m_moduleData.methods[methodId].JSMethodName; +} + std::vector RCTNativeModule::getMethods() { std::vector descs; @@ -58,13 +73,26 @@ void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) { + const char *moduleName = [m_moduleData.name UTF8String]; + const char *methodName = m_moduleData.methods[methodId].JSMethodName; + + dispatch_queue_t queue = m_moduleData.methodQueue; + const bool isSyncModule = queue == RCTJSThread; + + if (isSyncModule) { + BridgeNativeModulePerfLogger::syncMethodCallStart(moduleName, methodName); + BridgeNativeModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName); + } else { + BridgeNativeModulePerfLogger::asyncMethodCallStart(moduleName, methodName); + } + // capture by weak pointer so that we can safely use these variables in a callback __weak RCTBridge *weakBridge = m_bridge; __weak RCTModuleData *weakModuleData = m_moduleData; // The BatchedBridge version of this buckets all the callbacks by thread, and // queues one block on each. This is much simpler; we'll see how it goes and // iterate. - dispatch_block_t block = [weakBridge, weakModuleData, methodId, params = std::move(params), callId] { + dispatch_block_t block = [weakBridge, weakModuleData, methodId, params = std::move(params), callId, isSyncModule] { #ifdef WITH_FBSYSTRACE if (callId != -1) { fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId); @@ -72,13 +100,14 @@ #else (void)(callId); #endif - invokeInner(weakBridge, weakModuleData, methodId, std::move(params)); + invokeInner(weakBridge, weakModuleData, methodId, std::move(params), callId, isSyncModule ? Sync : Async); }; - dispatch_queue_t queue = m_moduleData.methodQueue; - if (queue == RCTJSThread) { + if (isSyncModule) { block(); + BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName); } else if (queue) { + BridgeNativeModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName); dispatch_async(queue, block); } @@ -90,17 +119,36 @@ m_moduleData.name); } #endif + + if (isSyncModule) { + BridgeNativeModulePerfLogger::syncMethodCallEnd(moduleName, methodName); + } else { + BridgeNativeModulePerfLogger::asyncMethodCallEnd(moduleName, methodName); + } } MethodCallResult RCTNativeModule::callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &¶ms) { - return invokeInner(m_bridge, m_moduleData, reactMethodId, params); + return invokeInner(m_bridge, m_moduleData, reactMethodId, params, 0, Sync); } -static MethodCallResult -invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic ¶ms) +static MethodCallResult invokeInner( + RCTBridge *bridge, + RCTModuleData *moduleData, + unsigned int methodId, + const folly::dynamic ¶ms, + int callId, + SchedulingContext context) { if (!bridge || !bridge.valid || !moduleData) { + if (context == Sync) { + /** + * NOTE: moduleName and methodName are "". This shouldn't be an issue because there can only be one ongoing sync + * call at a time, and when we call syncMethodCallFail, that one call should terminate. This is also an + * exceptional scenario, so it shouldn't occur often. + */ + BridgeNativeModulePerfLogger::syncMethodCallFail("N/A", "N/A"); + } return folly::none; } @@ -109,11 +157,44 @@ RCTLogError(@"Unknown methodID: %ud for module: %@", methodId, moduleData.name); } + const char *moduleName = [moduleData.name UTF8String]; + const char *methodName = moduleData.methods[methodId].JSMethodName; + + if (context == Async) { + BridgeNativeModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, (int32_t)callId); + BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionStart(moduleName, methodName, (int32_t)callId); + } + NSArray *objcParams = convertFollyDynamicToId(params); + + if (context == Sync) { + BridgeNativeModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName); + } + @try { + if (context == Sync) { + BridgeNativeModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName); + } else { + BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionEnd(moduleName, methodName, (int32_t)callId); + } + id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams]; + + if (context == Sync) { + BridgeNativeModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName); + BridgeNativeModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName); + } else { + BridgeNativeModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, (int32_t)callId); + } + return convertIdToFollyDynamic(result); } @catch (NSException *exception) { + if (context == Sync) { + BridgeNativeModulePerfLogger::syncMethodCallFail(moduleName, methodName); + } else { + BridgeNativeModulePerfLogger::asyncMethodCallExecutionFail(moduleName, methodName, (int32_t)callId); + } + // Pass on JS exceptions if ([exception.name hasPrefix:RCTFatalExceptionName]) { @throw exception; diff --git a/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm index 9da0483fad089a..afd1873bacfe4d 100644 --- a/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm @@ -15,8 +15,11 @@ using namespace facebook::react; +static NSString *const kRCTLegacyInteropChildComponentKey = @"childComponentView"; +static NSString *const kRCTLegacyInteropChildIndexKey = @"index"; + @implementation RCTLegacyViewManagerInteropComponentView { - NSMutableDictionary *_viewsToBeMounted; + NSMutableArray *_viewsToBeMounted; NSMutableArray *_viewsToBeUnmounted; RCTLegacyViewManagerInteropCoordinatorAdapter *_adapter; LegacyViewManagerInteropShadowNode::ConcreteState::Shared _state; @@ -27,7 +30,7 @@ - (instancetype)initWithFrame:(CGRect)frame if (self = [super initWithFrame:frame]) { static const auto defaultProps = std::make_shared(); _props = defaultProps; - _viewsToBeMounted = [NSMutableDictionary new]; + _viewsToBeMounted = [NSMutableArray new]; _viewsToBeUnmounted = [NSMutableArray new]; } @@ -89,7 +92,10 @@ - (void)prepareForRecycle - (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { - [_viewsToBeMounted setObject:childComponentView forKey:[NSNumber numberWithInteger:index]]; + [_viewsToBeMounted addObject:@{ + kRCTLegacyInteropChildIndexKey : [NSNumber numberWithInteger:index], + kRCTLegacyInteropChildComponentKey : childComponentView + }]; } - (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index @@ -126,8 +132,10 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask self.contentView = _adapter.paperView; } - for (NSNumber *key in _viewsToBeMounted) { - [_adapter.paperView insertReactSubview:_viewsToBeMounted[key] atIndex:key.integerValue]; + for (NSDictionary *mountInstruction in _viewsToBeMounted) { + NSNumber *index = mountInstruction[kRCTLegacyInteropChildIndexKey]; + UIView *childView = mountInstruction[kRCTLegacyInteropChildComponentKey]; + [_adapter.paperView insertReactSubview:childView atIndex:index.integerValue]; } [_viewsToBeMounted removeAllObjects]; diff --git a/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h b/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h index 60f9dca7efafd3..7b002cbf8bf66f 100644 --- a/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h +++ b/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h @@ -38,6 +38,9 @@ Class RCTSwitchCls(void) __attribute__((used)); Class RCTUnimplementedNativeViewCls(void) __attribute__((used)); Class RCTModalHostViewCls(void) __attribute__((used)); Class RCTImageCls(void) __attribute__((used)); +Class RCTParagraphCls(void) __attribute__((used)); +Class RCTTextInputCls(void) __attribute__((used)); +Class RCTViewCls(void) __attribute__((used)); #ifdef __cplusplus } diff --git a/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm b/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm index f56ec907d8467a..33cc39126fe494 100644 --- a/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm +++ b/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm @@ -27,6 +27,9 @@ {"UnimplementedNativeView", RCTUnimplementedNativeViewCls}, {"ModalHostView", RCTModalHostViewCls}, {"Image", RCTImageCls}, + {"Paragraph", RCTParagraphCls}, + {"TextInput", RCTTextInputCls}, + {"View", RCTViewCls}, }; auto p = sFabricComponentsClassMap.find(name); diff --git a/React/Fabric/Mounting/ComponentViews/SafeAreaView/RCTSafeAreaViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/SafeAreaView/RCTSafeAreaViewComponentView.mm index 982f63d5037a1a..6886660a73fd74 100644 --- a/React/Fabric/Mounting/ComponentViews/SafeAreaView/RCTSafeAreaViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/SafeAreaView/RCTSafeAreaViewComponentView.mm @@ -18,6 +18,7 @@ @implementation RCTSafeAreaViewComponentView { SafeAreaViewShadowNode::ConcreteState::Shared _state; + EdgeInsets _lastPaddingStateWasUpdatedWith; } - (instancetype)initWithFrame:(CGRect)frame @@ -40,11 +41,6 @@ - (UIEdgeInsets)_safeAreaInsets return UIEdgeInsetsZero; } -- (void)layoutSubviews -{ - [super layoutSubviews]; -} - - (void)safeAreaInsetsDidChange { [super safeAreaInsetsDidChange]; @@ -64,16 +60,16 @@ - (void)_updateStateIfNecessary insets.right = RCTRoundPixelValue(insets.right); insets.bottom = RCTRoundPixelValue(insets.bottom); - auto oldPadding = _state->getData().padding; auto newPadding = RCTEdgeInsetsFromUIEdgeInsets(insets); auto threshold = 1.0 / RCTScreenScale() + 0.01; // Size of a pixel plus some small threshold. - auto deltaPadding = newPadding - oldPadding; + auto deltaPadding = newPadding - _lastPaddingStateWasUpdatedWith; if (std::abs(deltaPadding.left) < threshold && std::abs(deltaPadding.top) < threshold && std::abs(deltaPadding.right) < threshold && std::abs(deltaPadding.bottom) < threshold) { return; } + _lastPaddingStateWasUpdatedWith = newPadding; _state->updateState(SafeAreaViewState{newPadding}); } @@ -90,6 +86,7 @@ - (void)prepareForRecycle { [super prepareForRecycle]; _state.reset(); + _lastPaddingStateWasUpdatedWith = {}; } + (ComponentDescriptorProvider)componentDescriptorProvider diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm index 33ff40c52e9060..cf36aafb33d4f3 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm @@ -6,6 +6,7 @@ */ #import "RCTEnhancedScrollView.h" +#import @implementation RCTEnhancedScrollView { __weak id _publicDelegate; @@ -84,4 +85,27 @@ - (instancetype)initWithFrame:(CGRect)frame return self; } +/* + * Automatically centers the content such that if the content is smaller than the + * ScrollView, we force it to be centered, but when you zoom or the content otherwise + * becomes larger than the ScrollView, there is no padding around the content but it + * can still fill the whole view. + */ +- (void)setContentOffset:(CGPoint)contentOffset +{ + if (_centerContent && !CGSizeEqualToSize(self.contentSize, CGSizeZero)) { + CGSize scrollViewSize = self.bounds.size; + if (self.contentSize.width <= scrollViewSize.width) { + contentOffset.x = -(scrollViewSize.width - self.contentSize.width) / 2.0; + } + if (self.contentSize.height <= scrollViewSize.height) { + contentOffset.y = -(scrollViewSize.height - self.contentSize.height) / 2.0; + } + } + + super.contentOffset = CGPointMake( + RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"), + RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y")); +} + @end diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 7a22c36c032eee..4868e6b5121576 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -163,6 +163,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset); } + if (oldScrollViewProps.contentOffset != newScrollViewProps.contentOffset) { + _scrollView.contentOffset = RCTCGPointFromPoint(newScrollViewProps.contentOffset); + } + // MAP_SCROLL_VIEW_PROP(scrollIndicatorInsets); // MAP_SCROLL_VIEW_PROP(snapToInterval); // MAP_SCROLL_VIEW_PROP(snapToAlignment); @@ -223,7 +227,8 @@ - (void)_updateStateWithContentOffset - (void)prepareForRecycle { - _scrollView.contentOffset = CGPointZero; + const auto &props = *std::static_pointer_cast(_props); + _scrollView.contentOffset = RCTCGPointFromPoint(props.contentOffset); _state.reset(); _isUserTriggeredScrolling = NO; [super prepareForRecycle]; diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index faaeb73736d7b3..42fa2ebe86ac2d 100644 --- a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -19,6 +19,7 @@ #import #import "RCTConversions.h" +#import "RCTFabricComponentsPlugins.h" using namespace facebook::react; @@ -169,3 +170,8 @@ - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point } @end + +Class RCTParagraphCls(void) +{ + return RCTParagraphComponentView.class; +} diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 1e7b92a2d104bb..8d936feba22625 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -20,6 +20,8 @@ #import "RCTTextInputNativeCommands.h" #import "RCTTextInputUtils.h" +#import "RCTFabricComponentsPlugins.h" + using namespace facebook::react; @interface RCTTextInputComponentView () @@ -62,7 +64,6 @@ - (instancetype)initWithFrame:(CGRect)frame auto &props = *defaultProps; _backedTextInputView = props.traits.multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init]; - _backedTextInputView.frame = self.bounds; _backedTextInputView.textInputDelegate = self; _ignoreNextTextInputCall = NO; _comingFromJS = NO; @@ -230,7 +231,7 @@ - (void)_setAttributedString:(NSAttributedString *)attributedString - (void)prepareForRecycle { [super prepareForRecycle]; - _backedTextInputView.attributedText = [[NSAttributedString alloc] init]; + _backedTextInputView.attributedText = nil; _mostRecentEventCount = 0; _state.reset(); _comingFromJS = NO; @@ -263,7 +264,7 @@ - (void)textInputDidBeginEditing auto const &props = *std::static_pointer_cast(_props); if (props.traits.clearTextOnFocus) { - _backedTextInputView.attributedText = [NSAttributedString new]; + _backedTextInputView.attributedText = nil; [self textInputDidChange]; } @@ -388,11 +389,10 @@ - (TextInputMetrics)_textInputMetrics - (void)_updateState { - NSAttributedString *attributedString = _backedTextInputView.attributedText; - if (!_state) { return; } + NSAttributedString *attributedString = _backedTextInputView.attributedText; auto data = _state->getData(); _lastStringStateWasUpdatedWith = attributedString; data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString); @@ -459,3 +459,8 @@ - (void)setTextAndSelection:(NSInteger)eventCount } @end + +Class RCTTextInputCls(void) +{ + return RCTTextInputComponentView.class; +} diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index a0f8d7af169061..09bc95cb33d57d 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -15,6 +15,7 @@ #import #import "RCTConversions.h" +#import "RCTFabricComponentsPlugins.h" using namespace facebook::react; @@ -179,7 +180,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & // `hitSlop` if (oldViewProps.hitSlop != newViewProps.hitSlop) { - self.hitTestEdgeInsets = RCTUIEdgeInsetsFromEdgeInsets(newViewProps.hitSlop); + self.hitTestEdgeInsets = {-newViewProps.hitSlop.top, + -newViewProps.hitSlop.left, + -newViewProps.hitSlop.bottom, + -newViewProps.hitSlop.right}; } // `overflow` @@ -518,7 +522,7 @@ - (NSString *)accessibilityLabel { auto const &accessibilityActions = _props->accessibilityActions; - if (accessibilityActions.size() == 0) { + if (accessibilityActions.empty()) { return nil; } @@ -584,3 +588,8 @@ - (NSString *)componentViewName_DO_NOT_USE_THIS_IS_BROKEN } @end + +Class RCTViewCls(void) +{ + return RCTViewComponentView.class; +} diff --git a/React/Fabric/Mounting/RCTComponentViewRegistry.mm b/React/Fabric/Mounting/RCTComponentViewRegistry.mm index 23d08cef831f64..b0dbe553b03ff4 100644 --- a/React/Fabric/Mounting/RCTComponentViewRegistry.mm +++ b/React/Fabric/Mounting/RCTComponentViewRegistry.mm @@ -189,7 +189,7 @@ - (RCTComponentViewDescriptor)_dequeueComponentViewWithComponentHandle:(Componen RCTAssertMainQueue(); auto &recycledViews = _recyclePool[componentHandle]; - if (recycledViews.size() == 0) { + if (recycledViews.empty()) { return [self.componentViewFactory createComponentViewWithComponentHandle:componentHandle]; } diff --git a/React/Fabric/Mounting/RCTMountingManager.h b/React/Fabric/Mounting/RCTMountingManager.h index eb4ca669c7a9f1..0e8771b8a8b907 100644 --- a/React/Fabric/Mounting/RCTMountingManager.h +++ b/React/Fabric/Mounting/RCTMountingManager.h @@ -26,8 +26,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id delegate; @property (nonatomic, strong) RCTComponentViewRegistry *componentViewRegistry; -@property (atomic, assign) BOOL useModernDifferentiatorMode; - /** * Schedule a mounting transaction to be performed on the main thread. * Can be called from any thread. diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 1aa7786d8591df..a64b1dd79ddbf0 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -266,10 +266,7 @@ - (void)performTransaction:(MountingCoordinator::Shared const &)mountingCoordina SystraceSection s("-[RCTMountingManager performTransaction:]"); RCTAssertMainQueue(); - auto differentiatorMode = - self.useModernDifferentiatorMode ? DifferentiatorMode::OptimizedMoves : DifferentiatorMode::Classic; - - auto transaction = mountingCoordinator->pullTransaction(differentiatorMode); + auto transaction = mountingCoordinator->pullTransaction(); if (!transaction.has_value()) { return; } @@ -277,7 +274,7 @@ - (void)performTransaction:(MountingCoordinator::Shared const &)mountingCoordina auto surfaceId = transaction->getSurfaceId(); auto &mutations = transaction->getMutations(); - if (mutations.size() == 0) { + if (mutations.empty()) { return; } diff --git a/React/Fabric/RCTScheduler.h b/React/Fabric/RCTScheduler.h index d38465591fe2c4..f85174c03f3877 100644 --- a/React/Fabric/RCTScheduler.h +++ b/React/Fabric/RCTScheduler.h @@ -64,6 +64,12 @@ NS_ASSUME_NONNULL_BEGIN - (facebook::react::MountingCoordinator::Shared)mountingCoordinatorWithSurfaceId:(facebook::react::SurfaceId)surfaceId; +- (void)onAnimationStarted; + +- (void)onAllAnimationsComplete; + +- (void)animationTick; + @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index 25821a72e6d913..d89af23b385638 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -7,10 +7,12 @@ #import "RCTScheduler.h" +#import #import #import #import #import +#include #import @@ -61,23 +63,82 @@ void schedulerDidClearJSResponder() override void *scheduler_; }; +class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, public RunLoopObserver::Delegate { + public: + LayoutAnimationDelegateProxy(void *scheduler) : scheduler_(scheduler) {} + virtual ~LayoutAnimationDelegateProxy() {} + + void onAnimationStarted() override + { + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler onAnimationStarted]; + } + + /** + * Called when the LayoutAnimation engine completes all pending animations. + */ + void onAllAnimationsComplete() override + { + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler onAllAnimationsComplete]; + } + + void activityDidChange(RunLoopObserver::Delegate const *delegate, RunLoopObserver::Activity activity) const + noexcept override + { + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler animationTick]; + } + + private: + void *scheduler_; +}; + @implementation RCTScheduler { std::shared_ptr _scheduler; + std::shared_ptr _animationDriver; std::shared_ptr _delegateProxy; + std::shared_ptr _layoutAnimationDelegateProxy; + RunLoopObserver::Unique _uiRunLoopObserver; + BOOL _layoutAnimationsEnabled; } - (instancetype)initWithToolbox:(facebook::react::SchedulerToolbox)toolbox { if (self = [super init]) { + auto reactNativeConfig = + toolbox.contextContainer->at>("ReactNativeConfig"); + _layoutAnimationsEnabled = reactNativeConfig->getBool("react_fabric:enabled_layout_animations_ios"); + _delegateProxy = std::make_shared((__bridge void *)self); - _scheduler = std::make_shared(toolbox, _delegateProxy.get()); + + if (_layoutAnimationsEnabled) { + _layoutAnimationDelegateProxy = std::make_shared((__bridge void *)self); + _animationDriver = std::make_unique(_layoutAnimationDelegateProxy.get()); + _uiRunLoopObserver = + toolbox.mainRunLoopObserverFactory(RunLoopObserver::Activity::BeforeWaiting, _layoutAnimationDelegateProxy); + _uiRunLoopObserver->setDelegate(_layoutAnimationDelegateProxy.get()); + } + + _scheduler = std::make_shared( + toolbox, (_animationDriver ? _animationDriver.get() : nullptr), _delegateProxy.get()); } return self; } +- (void)animationTick +{ + _scheduler->animationTick(); +} + - (void)dealloc { + if (_animationDriver) { + _animationDriver->setLayoutAnimationStatusDelegate(nullptr); + } + _animationDriver = nullptr; + _scheduler->setDelegate(nullptr); } @@ -90,7 +151,13 @@ - (void)startSurfaceWithSurfaceId:(SurfaceId)surfaceId SystraceSection s("-[RCTScheduler startSurfaceWithSurfaceId:...]"); auto props = convertIdToFollyDynamic(initialProps); - _scheduler->startSurface(surfaceId, RCTStringFromNSString(moduleName), props, layoutConstraints, layoutContext); + _scheduler->startSurface( + surfaceId, + RCTStringFromNSString(moduleName), + props, + layoutConstraints, + layoutContext, + (_animationDriver ? _animationDriver.get() : nullptr)); _scheduler->renderTemplateToSurface( surfaceId, props.getDefault("navigationConfig").getDefault("initialUITemplate", "").getString()); } @@ -127,4 +194,18 @@ - (ComponentDescriptor const *)findComponentDescriptorByHandle_DO_NOT_USE_THIS_I return _scheduler->findMountingCoordinator(surfaceId); } +- (void)onAnimationStarted +{ + if (_uiRunLoopObserver) { + _uiRunLoopObserver->enable(); + } +} + +- (void)onAllAnimationsComplete +{ + if (_uiRunLoopObserver) { + _uiRunLoopObserver->disable(); + } +} + @end diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index a199c0d866b31c..1f9cbc8d75de7e 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -28,11 +28,14 @@ #import #import #import +#import #import +#import #import #import #import "MainRunLoopEventBeat.h" +#import "PlatformRunLoopObserver.h" #import "RCTConversions.h" #import "RuntimeEventBeat.h" @@ -277,6 +280,8 @@ - (BOOL)resume - (RCTScheduler *)_createScheduler { + auto reactNativeConfig = _contextContainer->at>("ReactNativeConfig"); + auto componentRegistryFactory = [factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)]( EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) { @@ -290,24 +295,36 @@ - (RCTScheduler *)_createScheduler toolbox.contextContainer = _contextContainer; toolbox.componentRegistryFactory = componentRegistryFactory; toolbox.runtimeExecutor = runtimeExecutor; - - toolbox.synchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { - return std::make_unique(ownerBox, runtimeExecutor); + toolbox.mainRunLoopObserverFactory = [](RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner) { + return std::make_unique(activities, owner); }; - toolbox.asynchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { - return std::make_unique(ownerBox, runtimeExecutor); - }; + if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:enable_run_loop_based_event_beat_ios")) { + toolbox.synchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + auto runLoopObserver = + std::make_unique(RunLoopObserver::Activity::BeforeWaiting, ownerBox->owner); + return std::make_unique(std::move(runLoopObserver), runtimeExecutor); + }; + + toolbox.asynchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + auto runLoopObserver = + std::make_unique(RunLoopObserver::Activity::BeforeWaiting, ownerBox->owner); + return std::make_unique(std::move(runLoopObserver), runtimeExecutor); + }; + } else { + toolbox.synchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + return std::make_unique(ownerBox, runtimeExecutor); + }; + + toolbox.asynchronousEventBeatFactory = [runtimeExecutor](EventBeat::SharedOwnerBox const &ownerBox) { + return std::make_unique(ownerBox, runtimeExecutor); + }; + } RCTScheduler *scheduler = [[RCTScheduler alloc] initWithToolbox:toolbox]; scheduler.delegate = self; - auto reactNativeConfig = _contextContainer->at>("ReactNativeConfig"); - if (reactNativeConfig) { - _mountingManager.useModernDifferentiatorMode = - reactNativeConfig->getBool("react_fabric:enabled_optimized_moves_differ_ios"); - } - return scheduler; } diff --git a/React/Fabric/RCTSurfaceTouchHandler.mm b/React/Fabric/RCTSurfaceTouchHandler.mm index e2de89c668aa11..63c3699565abec 100644 --- a/React/Fabric/RCTSurfaceTouchHandler.mm +++ b/React/Fabric/RCTSurfaceTouchHandler.mm @@ -362,7 +362,7 @@ - (void)reset { [super reset]; - if (_activeTouches.size() != 0) { + if (!_activeTouches.empty()) { std::vector activeTouches; activeTouches.reserve(_activeTouches.size()); diff --git a/React/Fabric/Utils/PlatformRunLoopObserver.h b/React/Fabric/Utils/PlatformRunLoopObserver.h new file mode 100644 index 00000000000000..bbdf31fbe8a6be --- /dev/null +++ b/React/Fabric/Utils/PlatformRunLoopObserver.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +/* + * Concrete iOS-specific implementation of `RunLoopObserver` using + * `CFRunLoopObserver` under the hood. + */ +class PlatformRunLoopObserver : public RunLoopObserver { + public: + PlatformRunLoopObserver( + RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner, + CFRunLoopRef runLoop); + + ~PlatformRunLoopObserver(); + + virtual bool isOnRunLoopThread() const noexcept override; + + private: + void startObserving() const noexcept override; + void stopObserving() const noexcept override; + + CFRunLoopRef runLoop_; + CFRunLoopObserverRef mainRunLoopObserver_; +}; + +/* + * Convenience specialization of `PlatformRunLoopObserver` observing the main + * run loop. + */ +class MainRunLoopObserver final : public PlatformRunLoopObserver { + public: + MainRunLoopObserver( + RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner) + : PlatformRunLoopObserver(activities, owner, CFRunLoopGetMain()) {} +}; + +} // namespace react +} // namespace facebook diff --git a/React/Fabric/Utils/PlatformRunLoopObserver.mm b/React/Fabric/Utils/PlatformRunLoopObserver.mm new file mode 100644 index 00000000000000..09731eda3b08b6 --- /dev/null +++ b/React/Fabric/Utils/PlatformRunLoopObserver.mm @@ -0,0 +1,97 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "PlatformRunLoopObserver.h" + +#import + +namespace facebook { +namespace react { + +static CFRunLoopActivity toCFRunLoopActivity(RunLoopObserver::Activity activity) +{ + auto result = CFRunLoopActivity{}; + + if (RunLoopObserver::Activity(activity & RunLoopObserver::Activity::BeforeWaiting) == + RunLoopObserver::Activity::BeforeWaiting) { + result = result | kCFRunLoopBeforeWaiting; + } + + if (RunLoopObserver::Activity(activity & RunLoopObserver::Activity::AfterWaiting) == + RunLoopObserver::Activity::AfterWaiting) { + result = result | kCFRunLoopAfterWaiting; + } + + return result; +} + +static RunLoopObserver::Activity toRunLoopActivity(CFRunLoopActivity activity) +{ + auto result = RunLoopObserver::Activity{}; + + if (CFRunLoopActivity(activity & kCFRunLoopBeforeWaiting) == kCFRunLoopBeforeWaiting) { + result = RunLoopObserver::Activity(result | RunLoopObserver::Activity::BeforeWaiting); + } + + if (CFRunLoopActivity(activity & kCFRunLoopAfterWaiting) == kCFRunLoopAfterWaiting) { + result = RunLoopObserver::Activity(result | RunLoopObserver::Activity::AfterWaiting); + } + + return result; +} + +PlatformRunLoopObserver::PlatformRunLoopObserver( + RunLoopObserver::Activity activities, + RunLoopObserver::WeakOwner const &owner, + CFRunLoopRef runLoop) + : RunLoopObserver(activities, owner), runLoop_(runLoop) +{ + // A value (not a reference) to be captured by the block. + auto weakOwner = owner; + + // The documentation for `CFRunLoop` family API states that all of the methods are thread-safe. + // See "Thread Safety and Run Loop Objects" section of the "Threading Programming Guide" for more details. + mainRunLoopObserver_ = CFRunLoopObserverCreateWithHandler( + NULL /* allocator */, + toCFRunLoopActivity(activities_) /* activities */, + true /* repeats */, + 0 /* order */, + ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + auto strongOwner = weakOwner.lock(); + if (!strongOwner) { + return; + } + + this->activityDidChange(toRunLoopActivity(activity)); + }); + + assert(mainRunLoopObserver_); +} + +PlatformRunLoopObserver::~PlatformRunLoopObserver() +{ + stopObserving(); + CFRelease(mainRunLoopObserver_); +} + +void PlatformRunLoopObserver::startObserving() const noexcept +{ + CFRunLoopAddObserver(runLoop_, mainRunLoopObserver_, kCFRunLoopCommonModes); +} + +void PlatformRunLoopObserver::stopObserving() const noexcept +{ + CFRunLoopRemoveObserver(runLoop_, mainRunLoopObserver_, kCFRunLoopCommonModes); +} + +bool PlatformRunLoopObserver::isOnRunLoopThread() const noexcept +{ + return CFRunLoopGetCurrent() == runLoop_; +} + +} // namespace react +} // namespace facebook diff --git a/React/Views/RCTConvert+Transform.m b/React/Views/RCTConvert+Transform.m index 94e5dcd6cd91c4..06a0db3b4863e3 100644 --- a/React/Views/RCTConvert+Transform.m +++ b/React/Views/RCTConvert+Transform.m @@ -65,6 +65,7 @@ + (CATransform3D)CATransform3D:(id)json CGFloat zeroScaleThreshold = FLT_EPSILON; + CATransform3D next; for (NSDictionary *transformConfig in (NSArray *)json) { if (transformConfig.count != 1) { RCTLogConvertError(json, @"a CATransform3D. You must specify exactly one property per transform object."); @@ -74,10 +75,13 @@ + (CATransform3D)CATransform3D:(id)json id value = transformConfig[property]; if ([property isEqualToString:@"matrix"]) { - transform = [self CATransform3DFromMatrix:value]; + next = [self CATransform3DFromMatrix:value]; + transform = CATransform3DConcat(next, transform); } else if ([property isEqualToString:@"perspective"]) { - transform.m34 = -1 / [value floatValue]; + next = CATransform3DIdentity; + next.m34 = -1 / [value floatValue]; + transform = CATransform3DConcat(next, transform); } else if ([property isEqualToString:@"rotateX"]) { CGFloat rotate = [self convertToRadians:value]; @@ -123,11 +127,15 @@ + (CATransform3D)CATransform3D:(id)json } else if ([property isEqualToString:@"skewX"]) { CGFloat skew = [self convertToRadians:value]; - transform.m21 = tanf(skew); + next = CATransform3DIdentity; + next.m21 = tanf(skew); + transform = CATransform3DConcat(next, transform); } else if ([property isEqualToString:@"skewY"]) { CGFloat skew = [self convertToRadians:value]; - transform.m12 = tanf(skew); + next = CATransform3DIdentity; + next.m12 = tanf(skew); + transform = CATransform3DConcat(next, transform); } else { RCTLogError(@"Unsupported transform type for a CATransform3D: %@.", property); diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 595bfafb74804f..23d9fd5912bc9a 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -98,12 +98,12 @@ task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy task prepareHermes() { def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine") if (!hermesPackagePath) { - throw new GradleScriptException("Could not find the hermes-engine npm package") + throw new GradleScriptException("Could not find the hermes-engine npm package", null) } def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar") if (!hermesAAR.exists()) { - throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"") + throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"", null) } def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" }) @@ -165,12 +165,12 @@ task prepareJSC { doLast { def jscPackagePath = findNodeModulePath(projectDir, "jsc-android") if (!jscPackagePath) { - throw new GradleScriptException("Could not find the jsc-android npm package") + throw new GradleScriptException("Could not find the jsc-android npm package", null) } def jscDist = file("$jscPackagePath/dist") if (!jscDist.exists()) { - throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory") + throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory", null) } def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile @@ -454,7 +454,7 @@ dependencies { extractJNI("com.facebook.fbjni:fbjni:0.0.2") testImplementation("junit:junit:${JUNIT_VERSION}") - testImplementation("org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}") + testImplementation("org.powermock:powermock-api-mockito2:${POWERMOCK_VERSION}") testImplementation("org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}") testImplementation("org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}") testImplementation("org.mockito:mockito-core:${MOCKITO_CORE_VERSION}") diff --git a/ReactAndroid/gradle.properties b/ReactAndroid/gradle.properties index f7a5d3dc93b57c..1448ea45a04f9a 100644 --- a/ReactAndroid/gradle.properties +++ b/ReactAndroid/gradle.properties @@ -6,9 +6,9 @@ POM_NAME=ReactNative POM_ARTIFACT_ID=react-native POM_PACKAGING=aar -MOCKITO_CORE_VERSION=2.19.1 -POWERMOCK_VERSION=1.6.2 -ROBOLECTRIC_VERSION=3.0 +MOCKITO_CORE_VERSION=2.26.0 +POWERMOCK_VERSION=2.0.2 +ROBOLECTRIC_VERSION=4.3.1 JUNIT_VERSION=4.12 FEST_ASSERT_CORE_VERSION=2.0M10 diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/network/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/network/BUCK index ebfebaf7fe9f4e..980eda5d8d0601 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/network/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/network/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "network", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK index 798f91566e8184..0b29ef31268c22 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK @@ -28,7 +28,6 @@ rn_android_library( react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"), react_native_target("java/com/facebook/react/modules/share:share"), react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"), - react_native_target("java/com/facebook/react/modules/timepicker:timepicker"), react_native_target("java/com/facebook/react/touch:touch"), react_native_target("java/com/facebook/react/uimanager:uimanager"), react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TimePickerDialogTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TimePickerDialogTestCase.java deleted file mode 100644 index 4325a3a6af4c46..00000000000000 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TimePickerDialogTestCase.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.tests; - -import android.app.TimePickerDialog; -import android.content.DialogInterface; -import androidx.fragment.app.DialogFragment; -import com.facebook.react.bridge.BaseJavaModule; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeMap; -import com.facebook.react.modules.timepicker.TimePickerDialogModule; -import com.facebook.react.testing.ReactAppInstrumentationTestCase; -import com.facebook.react.testing.ReactInstanceSpecForTest; -import java.util.ArrayList; -import java.util.List; - -/** Test case for {@link TimePickerDialogModule} options and callbacks. */ -public class TimePickerDialogTestCase extends ReactAppInstrumentationTestCase { - - private static interface TimePickerDialogTestModule extends JavaScriptModule { - public void showTimePickerDialog(WritableMap options); - } - - private static class TimePickerDialogRecordingModule extends BaseJavaModule { - - private final List mTimes = new ArrayList(); - private int mDismissed = 0; - private int mErrors = 0; - - @Override - public String getName() { - return "TimePickerDialogRecordingModule"; - } - - @ReactMethod - public void recordTime(int hour, int minute) { - mTimes.add(new Integer[] {hour, minute}); - } - - @ReactMethod - public void recordDismissed() { - mDismissed++; - } - - @ReactMethod - public void recordError() { - mErrors++; - } - - public List getTimes() { - return new ArrayList(mTimes); - } - - public int getDismissed() { - return mDismissed; - } - - public int getErrors() { - return mErrors; - } - } - - final TimePickerDialogRecordingModule mRecordingModule = new TimePickerDialogRecordingModule(); - - @Override - protected ReactInstanceSpecForTest createReactInstanceSpecForTest() { - return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule); - } - - @Override - protected String getReactApplicationKeyUnderTest() { - return "TimePickerDialogTestApp"; - } - - private TimePickerDialogTestModule getTestModule() { - return getReactContext().getCatalystInstance().getJSModule(TimePickerDialogTestModule.class); - } - - private DialogFragment showDialog(WritableMap options) { - getTestModule().showTimePickerDialog(options); - - waitForBridgeAndUIIdle(); - getInstrumentation().waitForIdleSync(); - - return (DialogFragment) - getActivity() - .getSupportFragmentManager() - .findFragmentByTag(TimePickerDialogModule.FRAGMENT_TAG); - } - - public void testShowBasicTimePicker() { - final DialogFragment fragment = showDialog(null); - - assertNotNull(fragment); - } - - public void testPresetTimeAndCallback() throws Throwable { - final WritableMap options = new WritableNativeMap(); - options.putInt("hour", 4); - options.putInt("minute", 5); - - final DialogFragment fragment = showDialog(options); - - List recordedTimes = mRecordingModule.getTimes(); - assertEquals(0, recordedTimes.size()); - - runTestOnUiThread( - new Runnable() { - @Override - public void run() { - ((TimePickerDialog) fragment.getDialog()) - .getButton(DialogInterface.BUTTON_POSITIVE) - .performClick(); - } - }); - - getInstrumentation().waitForIdleSync(); - waitForBridgeAndUIIdle(); - - assertEquals(0, mRecordingModule.getErrors()); - assertEquals(0, mRecordingModule.getDismissed()); - - recordedTimes = mRecordingModule.getTimes(); - assertEquals(1, recordedTimes.size()); - assertEquals(4, (int) recordedTimes.get(0)[0]); - assertEquals(5, (int) recordedTimes.get(0)[1]); - } - - public void testDismissCallback() throws Throwable { - final DialogFragment fragment = showDialog(null); - - assertEquals(0, mRecordingModule.getDismissed()); - - runTestOnUiThread( - new Runnable() { - @Override - public void run() { - fragment.getDialog().dismiss(); - } - }); - - getInstrumentation().waitForIdleSync(); - waitForBridgeAndUIIdle(); - - assertEquals(0, mRecordingModule.getErrors()); - assertEquals(0, mRecordingModule.getTimes().size()); - assertEquals(1, mRecordingModule.getDismissed()); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/debug/holder/BUCK b/ReactAndroid/src/main/java/com/facebook/debug/holder/BUCK index c1ee4ded0329dd..2daef176010bf5 100644 --- a/ReactAndroid/src/main/java/com/facebook/debug/holder/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/debug/holder/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_libra rn_android_library( name = "holder", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/debug/tags/BUCK b/ReactAndroid/src/main/java/com/facebook/debug/tags/BUCK index 876be9f2ba6753..3e2e015c78e8d2 100644 --- a/ReactAndroid/src/main/java/com/facebook/debug/tags/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/debug/tags/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_libra rn_android_library( name = "tags", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/BUCK b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/BUCK index cec027de58b101..94067e4f20f33d 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "IS_OSS_BU rn_android_library( name = "FBReactNativeSpec", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = ["PUBLIC"], deps = [ react_native_dep("third-party/java/jsr-305:jsr-305"), @@ -34,7 +34,7 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID,), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeTimePickerAndroidSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeTimePickerAndroidSpec.java deleted file mode 100644 index e9707f4f3b3c86..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeTimePickerAndroidSpec.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - *

This source code is licensed under the MIT license found in the LICENSE file in the root - * directory of this source tree. - * - *

Generated by an internal genrule from Flow types. - * - * @generated - * @nolint - */ - -package com.facebook.fbreact.specs; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReactModuleWithSpec; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.turbomodule.core.interfaces.TurboModule; - -public abstract class NativeTimePickerAndroidSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { - public NativeTimePickerAndroidSpec(ReactApplicationContext reactContext) { - super(reactContext); - } - - @ReactMethod - public abstract void open(ReadableMap options, Promise promise); -} diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeUIManagerSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeUIManagerSpec.java index 1f9cdce0522832..03e3fe9bb0ce48 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeUIManagerSpec.java +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeUIManagerSpec.java @@ -42,9 +42,6 @@ public NativeUIManagerSpec(ReactApplicationContext reactContext) { public abstract void configureNextLayoutAnimation(ReadableMap config, Callback callback, Callback errorCallback); - @ReactMethod - public abstract void playTouchSound(); - @ReactMethod public abstract void blur(Double reactTag); diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp index 069f845b9fde02..62189bc3aba3e3 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec-generated.cpp @@ -2059,26 +2059,6 @@ namespace facebook { - } - - } // namespace react -} // namespace facebook -namespace facebook { - namespace react { - - - static facebook::jsi::Value __hostFunction_NativeTimePickerAndroidSpecJSI_open(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { - return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, "open", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V", args, count); - } - - - NativeTimePickerAndroidSpecJSI::NativeTimePickerAndroidSpecJSI(const JavaTurboModule::InitParams ¶ms) - : JavaTurboModule(params) { - - methodMap_["open"] = MethodMetadata {1, __hostFunction_NativeTimePickerAndroidSpecJSI_open}; - - - } } // namespace react @@ -2170,10 +2150,6 @@ namespace facebook { return static_cast(turboModule).invokeJavaMethod(rt, ArrayKind, "getDefaultEventTypes", "()Lcom/facebook/react/bridge/WritableArray;", args, count); } - static facebook::jsi::Value __hostFunction_NativeUIManagerSpecJSI_playTouchSound(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { - return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "playTouchSound", "()V", args, count); - } - static facebook::jsi::Value __hostFunction_NativeUIManagerSpecJSI_lazilyLoadView(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, "lazilyLoadView", "(Ljava/lang/String;)Lcom/facebook/react/bridge/WritableMap;", args, count); } @@ -2280,9 +2256,6 @@ namespace facebook { methodMap_["getDefaultEventTypes"] = MethodMetadata {0, __hostFunction_NativeUIManagerSpecJSI_getDefaultEventTypes}; - methodMap_["playTouchSound"] = MethodMetadata {0, __hostFunction_NativeUIManagerSpecJSI_playTouchSound}; - - methodMap_["lazilyLoadView"] = MethodMetadata {1, __hostFunction_NativeUIManagerSpecJSI_lazilyLoadView}; diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec.h b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec.h index eb22a864c9bfa7..bb12df936070cd 100644 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec.h +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/jni/FBReactNativeSpec.h @@ -731,20 +731,6 @@ namespace facebook { } // namespace react } // namespace facebook -namespace facebook { - namespace react { - /** - * C++ class for module 'TimePickerAndroid' - */ - - class JSI_EXPORT NativeTimePickerAndroidSpecJSI : public JavaTurboModule { - public: - NativeTimePickerAndroidSpecJSI(const JavaTurboModule::InitParams ¶ms); - - }; - } // namespace react -} // namespace facebook - namespace facebook { namespace react { /** diff --git a/ReactAndroid/src/main/java/com/facebook/hermes/instrumentation/BUCK b/ReactAndroid/src/main/java/com/facebook/hermes/instrumentation/BUCK index 3b4ea04dbfdff0..439ae4b4743578 100644 --- a/ReactAndroid/src/main/java/com/facebook/hermes/instrumentation/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/hermes/instrumentation/BUCK @@ -11,7 +11,7 @@ rn_android_library( rn_android_library( name = "hermes_samplingprofiler", srcs = ["HermesSamplingProfiler.java"], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = ["PUBLIC"], deps = [ react_native_dep("java/com/facebook/proguard/annotations:annotations"), @@ -30,7 +30,7 @@ rn_xplat_cxx_library( headers = ["HermesSamplingProfiler.h"], header_namespace = "", compiler_flags = ["-fexceptions"], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = ANDROID, soname = "libjsijniprofiler.$(ext)", visibility = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK index c8728a8d441754..f18cc78f8f02c7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "react", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:appcompat"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 59ccbbedefb4b8..63ac2ec11dfb62 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -1152,18 +1152,30 @@ private void attachRootViewToInstance(final ReactRoot reactRoot) { final int rootTag; - if (reactRoot.getUIManagerType() == FABRIC) { - rootTag = - uiManager.startSurface( - reactRoot.getRootViewGroup(), - reactRoot.getJSModuleName(), - initialProperties == null - ? new WritableNativeMap() - : Arguments.fromBundle(initialProperties), - reactRoot.getWidthMeasureSpec(), - reactRoot.getHeightMeasureSpec()); - reactRoot.setRootViewTag(rootTag); - reactRoot.setShouldLogContentAppeared(true); + if (ReactFeatureFlags.enableFabricStartSurfaceWithLayoutMetrics) { + if (reactRoot.getUIManagerType() == FABRIC) { + rootTag = + uiManager.startSurface( + reactRoot.getRootViewGroup(), + reactRoot.getJSModuleName(), + initialProperties == null + ? new WritableNativeMap() + : Arguments.fromBundle(initialProperties), + reactRoot.getWidthMeasureSpec(), + reactRoot.getHeightMeasureSpec()); + reactRoot.setRootViewTag(rootTag); + reactRoot.setShouldLogContentAppeared(true); + } else { + rootTag = + uiManager.addRootView( + reactRoot.getRootViewGroup(), + initialProperties == null + ? new WritableNativeMap() + : Arguments.fromBundle(initialProperties), + reactRoot.getInitialUITemplate()); + reactRoot.setRootViewTag(rootTag); + reactRoot.runApplication(); + } } else { rootTag = uiManager.addRootView( @@ -1173,8 +1185,17 @@ private void attachRootViewToInstance(final ReactRoot reactRoot) { : Arguments.fromBundle(initialProperties), reactRoot.getInitialUITemplate()); reactRoot.setRootViewTag(rootTag); - reactRoot.runApplication(); + if (reactRoot.getUIManagerType() == FABRIC) { + // Fabric requires to call updateRootLayoutSpecs before starting JS Application, + // this ensures the root will hace the correct pointScaleFactor. + uiManager.updateRootLayoutSpecs( + rootTag, reactRoot.getWidthMeasureSpec(), reactRoot.getHeightMeasureSpec()); + reactRoot.setShouldLogContentAppeared(true); + } else { + reactRoot.runApplication(); + } } + Systrace.beginAsyncSection( TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag); UiThreadUtil.runOnUiThread( @@ -1303,6 +1324,9 @@ private ReactApplicationContext createReactContext( } } } + if (ReactFeatureFlags.eagerInitializeFabric) { + catalystInstance.getJSIModule(JSIModuleType.UIManager); + } if (mBridgeIdleDebugListener != null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK b/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK index 57e9752b640e08..c3f9021b086d2e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/BUCK @@ -6,7 +6,7 @@ rn_android_library( "*.java", ]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index ead07f9512229b..e67723c7b13f76 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -7,7 +7,9 @@ package com.facebook.react.animated; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import com.facebook.common.logging.FLog; import com.facebook.fbreact.specs.NativeAnimatedModuleSpec; import com.facebook.infer.annotation.Assertions; @@ -16,6 +18,8 @@ import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; @@ -26,8 +30,9 @@ import com.facebook.react.uimanager.NativeViewHierarchyManager; import com.facebook.react.uimanager.UIBlock; import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.UIManagerModuleListener; import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * Module that exposes interface for creating and managing animated nodes on the "native" side. @@ -75,7 +80,7 @@ */ @ReactModule(name = NativeAnimatedModule.NAME) public class NativeAnimatedModule extends NativeAnimatedModuleSpec - implements LifecycleEventListener, UIManagerModuleListener { + implements LifecycleEventListener, UIManagerListener { public static final String NAME = "NativeAnimatedModule"; @@ -83,10 +88,13 @@ private interface UIThreadOperation { void execute(NativeAnimatedNodesManager animatedNodesManager); } - private final GuardedFrameCallback mAnimatedFrameCallback; + @NonNull private final GuardedFrameCallback mAnimatedFrameCallback; private final ReactChoreographer mReactChoreographer; - private ArrayList mOperations = new ArrayList<>(); - private ArrayList mPreOperations = new ArrayList<>(); + @NonNull private List mOperations = new ArrayList<>(); + @NonNull private List mPreOperations = new ArrayList<>(); + + @NonNull private List mPreOperationsUIBlock = new ArrayList<>(); + @NonNull private List mOperationsUIBlock = new ArrayList<>(); private @Nullable NativeAnimatedNodesManager mNodesManager; @@ -132,7 +140,7 @@ public void initialize() { reactApplicationContext.addLifecycleEventListener(this); UIManagerModule uiManager = Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class)); - uiManager.addUIManagerListener(this); + uiManager.addUIManagerEventListener(this); } } @@ -141,35 +149,94 @@ public void onHostResume() { enqueueFrameCallback(); } + // For FabricUIManager @Override - public void willDispatchViewUpdates(final UIManagerModule uiManager) { + @UiThread + public void willDispatchPreMountItems() { + if (mPreOperationsUIBlock.size() != 0) { + List preOperations = mPreOperationsUIBlock; + mPreOperationsUIBlock = new ArrayList<>(); + + for (UIBlock op : preOperations) { + op.execute(null); + } + } + } + + // For FabricUIManager + @Override + @UiThread + public void willDispatchMountItems() { + if (mOperationsUIBlock.size() != 0) { + List operations = mOperationsUIBlock; + mOperationsUIBlock = new ArrayList<>(); + + for (UIBlock op : operations) { + op.execute(null); + } + } + } + + // For non-FabricUIManager + @Override + @UiThread + public void willDispatchViewUpdates(final UIManager uiManager) { if (mOperations.isEmpty() && mPreOperations.isEmpty()) { return; } - final ArrayList preOperations = mPreOperations; - final ArrayList operations = mOperations; + + final AtomicBoolean hasRunPreOperations = new AtomicBoolean(false); + final AtomicBoolean hasRunOperations = new AtomicBoolean(false); + final List preOperations = mPreOperations; + final List operations = mOperations; mPreOperations = new ArrayList<>(); mOperations = new ArrayList<>(); - uiManager.prependUIBlock( + + // This is kind of a hack. Basically UIManagerListener cannot import UIManagerModule + // (that would cause an import cycle) and they're not in the same package. But, + // UIManagerModule is the only thing that calls `willDispatchViewUpdates` so we + // know this is safe. + // This goes away entirely in Fabric/Venice. + UIManagerModule uiManagerModule = (UIManagerModule) uiManager; + + UIBlock preOperationsUIBlock = new UIBlock() { @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { + if (!hasRunPreOperations.compareAndSet(false, true)) { + return; + } + NativeAnimatedNodesManager nodesManager = getNodesManager(); for (UIThreadOperation operation : preOperations) { operation.execute(nodesManager); } } - }); - uiManager.addUIBlock( + }; + + UIBlock operationsUIBlock = new UIBlock() { @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { + if (!hasRunOperations.compareAndSet(false, true)) { + return; + } + NativeAnimatedNodesManager nodesManager = getNodesManager(); for (UIThreadOperation operation : operations) { operation.execute(nodesManager); } } - }); + }; + + // Queue up operations for Fabric + mPreOperationsUIBlock.add(preOperationsUIBlock); + mOperationsUIBlock.add(operationsUIBlock); + + // Here we queue up the UI Blocks for the old, non-Fabric UIManager. + // We queue them in both systems, let them race, and see which wins. + uiManagerModule.prependUIBlock(preOperationsUIBlock); + uiManagerModule.addUIBlock(operationsUIBlock); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK b/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK index 53ac6202190a9c..fcf60e30696589 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK @@ -21,7 +21,7 @@ rn_android_library( exclude = INTERFACES, ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], proguard_config = "reactnative.pro", provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), @@ -66,7 +66,7 @@ rn_android_library( name = "interfaces", srcs = glob(INTERFACES), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], proguard_config = "reactnative.pro", provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java index 170fc7fcd3f25e..4edebdcf00ea54 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -91,4 +91,19 @@ int startSurface( * @param eventType */ void sendAccessibilityEvent(int reactTag, int eventType); + + /** + * Register a {@link UIManagerListener} with this UIManager to receive lifecycle callbacks. + * + * @param listener + */ + void addUIManagerEventListener(UIManagerListener listener); + + /** + * Unregister a {@link UIManagerListener} from this UIManager to stop receiving lifecycle + * callbacks. + * + * @param listener + */ + void removeUIManagerEventListener(UIManagerListener listener); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java new file mode 100644 index 00000000000000..216d38b00d6495 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge; + +/** Listener used to hook into the UIManager update process. */ +public interface UIManagerListener { + /** + * Called right before view updates are dispatched at the end of a batch. This is useful if a + * module needs to add UIBlocks to the queue before it is flushed. + */ + void willDispatchViewUpdates(UIManager uiManager); + /** Called on the UI thread right before normal mount items are executed. */ + void willDispatchMountItems(); + /** Called on the UI thread right before premount items are executed. */ + void willDispatchPreMountItems(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK index 0e5a4b52caeac1..ef52fb6fb78abf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK @@ -11,7 +11,7 @@ rn_android_library( exclude = SUB_PROJECTS, ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/network/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/network/BUCK index 86005fbc76ae79..25f39b59688c28 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/network/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/common/network/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_libra rn_android_library( name = "network", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/BUCK b/ReactAndroid/src/main/java/com/facebook/react/config/BUCK index 347452197e226b..6120fdec936c2d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/config/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library") rn_android_library( name = "config", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index c04a883d5fb5cc..c06d0e0c3508d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -38,6 +38,13 @@ public class ReactFeatureFlags { */ public static boolean useViewManagerDelegates = false; + /** + * Should this application use a {@link com.facebook.react.uimanager.ViewManagerDelegate} (if + * provided) to execute the view commands. If {@code false}, then {@code receiveCommand} method + * inside view manager will be called instead. + */ + public static boolean useViewManagerDelegatesForCommands = false; + /** * Should this application use Catalyst Teardown V2? This is an experiment to use a V2 of the * CatalystInstanceImpl `destroy` method. @@ -76,4 +83,10 @@ public class ReactFeatureFlags { * remove this when bug is fixed */ public static boolean enableTransitionLayoutOnlyViewCleanup = false; + + /** Feature flag to configure eager initialization of Fabric */ + public static boolean eagerInitializeFabric = false; + + /** Feature flag to configure initialization of Fabric surfaces. */ + public static boolean enableFabricStartSurfaceWithLayoutMetrics = true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK index 1c2b6c72e761b4..7680650ab6a62f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "devsupport", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], manifest = "AndroidManifest.xml", provided_deps = [ react_native_dep("third-party/android/androidx:core"), @@ -47,7 +47,7 @@ rn_android_library( name = "interfaces", srcs = glob(["interfaces/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 5c5bad9eb4a3a2..b539282d635215 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -421,12 +421,13 @@ private boolean getJSMinifyMode() { private String createBundleURL(String mainModuleID, BundleType type, String host) { return String.format( Locale.US, - "http://%s/%s.%s?platform=android&dev=%s&minify=%s", + "http://%s/%s.%s?platform=android&dev=%s&minify=%s&app=%s", host, mainModuleID, type.typeID(), getDevMode(), - getJSMinifyMode()); + getJSMinifyMode(), + mPackageName); } private String createBundleURL(String mainModuleID, BundleType type) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK index 4aa1d97440a5f3..9ebcd431161f44 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK @@ -9,7 +9,7 @@ rn_android_library( "mounting/**/*.java", ]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/Binding.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/Binding.java index 81ab4e76829cff..d0abc11e6dc651 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/Binding.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/Binding.java @@ -70,6 +70,8 @@ public native void setConstraints( boolean isRTL, boolean doLeftAndRightSwapInRTL); + public native void driveCxxAnimations(); + public void register( @NonNull JavaScriptContextHolder jsContext, @NonNull FabricUIManager fabricUIManager, diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java index 0caf99934dd9f2..62e020c7118bdd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java @@ -33,7 +33,6 @@ import com.facebook.react.fabric.mounting.mountitems.SendAccessibilityEvent; import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; -import com.facebook.react.fabric.mounting.mountitems.UpdateLocalDataMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdatePaddingMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdateStateMountItem; @@ -123,7 +122,6 @@ private static void loadClasses() { SendAccessibilityEvent.class.getClass(); UpdateEventEmitterMountItem.class.getClass(); UpdateLayoutMountItem.class.getClass(); - UpdateLocalDataMountItem.class.getClass(); UpdatePaddingMountItem.class.getClass(); UpdatePropsMountItem.class.getClass(); UpdateStateMountItem.class.getClass(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index e5239c8db9835d..b80ee9f25c563c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -42,6 +42,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.RetryableMountingLayerException; import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; import com.facebook.react.config.ReactFeatureFlags; @@ -63,7 +64,6 @@ import com.facebook.react.fabric.mounting.mountitems.SendAccessibilityEvent; import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; -import com.facebook.react.fabric.mounting.mountitems.UpdateLocalDataMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdatePaddingMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdateStateMountItem; @@ -83,6 +83,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; @SuppressLint("MissingNativeLoadLibrary") public class FabricUIManager implements UIManager, LifecycleEventListener { @@ -124,6 +125,9 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { @NonNull private List mViewCommandMountItems = new ArrayList<>(); + @NonNull + private final CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); + @GuardedBy("mMountItemsLock") @NonNull private List mMountItems = new ArrayList<>(); @@ -143,6 +147,8 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { */ private volatile boolean mDestroyed = false; + private boolean mDriveCxxAnimations = false; + private long mRunStartTime = 0l; private long mBatchedExecutionTime = 0l; private long mDispatchViewUpdatesTime = 0l; @@ -395,14 +401,6 @@ private MountItem updatePropsMountItem(int reactTag, ReadableMap map) { return new UpdatePropsMountItem(reactTag, map); } - @DoNotStrip - @SuppressWarnings("unused") - @AnyThread - @ThreadConfined(ANY) - private MountItem updateLocalDataMountItem(int reactTag, ReadableMap newLocalData) { - return new UpdateLocalDataMountItem(reactTag, newLocalData); - } - @DoNotStrip @SuppressWarnings("unused") @AnyThread @@ -464,7 +462,7 @@ private long measure( float maxWidth, float minHeight, float maxHeight, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { ReactContext context = rootTag < 0 ? mReactApplicationContext : mReactContextForRootTag.get(rootTag); return mMountingManager.measure( @@ -507,6 +505,14 @@ public void synchronouslyUpdateViewOnUIThread(int reactTag, @NonNull ReadableMap } } + public void addUIManagerEventListener(UIManagerListener listener) { + mListeners.add(listener); + } + + public void removeUIManagerEventListener(UIManagerListener listener) { + mListeners.remove(listener); + } + /** * This method enqueues UI operations directly to the UI thread. This might change in the future * to enforce execution order using {@link ReactChoreographer#CallbackType}. @@ -588,6 +594,10 @@ private void tryDispatchMountItems() { return; } + for (UIManagerListener listener : mListeners) { + listener.willDispatchMountItems(); + } + final boolean didDispatchItems; try { didDispatchItems = dispatchMountItems(); @@ -773,12 +783,16 @@ private boolean dispatchMountItems() { // exception but never crash in debug. // It's not clear that logging this is even useful, because these events are very // common, mundane, and there's not much we can do about them currently. - ReactSoftException.logSoftException( - TAG, - new ReactNoCrashSoftException( - "Caught exception executing retryable mounting layer instruction: " - + mountItem.toString(), - e)); + if (mountItem instanceof DispatchCommandMountItem) { + ReactSoftException.logSoftException( + TAG, + new ReactNoCrashSoftException( + "Caught exception executing retryable mounting layer instruction: " + + mountItem.toString(), + e)); + } else { + throw e; + } } } mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime; @@ -797,6 +811,10 @@ private void dispatchPreMountItems(long frameTimeNanos) { // reentering during dispatchPreMountItems mInDispatch = true; + for (UIManagerListener listener : mListeners) { + listener.willDispatchPreMountItems(); + } + try { while (true) { long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000); @@ -972,6 +990,20 @@ public void profileNextBatch() { // TODO T31905686: Remove this method and add support for multi-threading performance counters } + // Called from Binding.cpp + @DoNotStrip + @AnyThread + public void onAnimationStarted() { + mDriveCxxAnimations = true; + } + + // Called from Binding.cpp + @DoNotStrip + @AnyThread + public void onAllAnimationsComplete() { + mDriveCxxAnimations = false; + } + @Override public Map getPerformanceCounters() { HashMap performanceCounters = new HashMap<>(); @@ -1007,11 +1039,18 @@ public void doFrameGuarded(long frameTimeNanos) { return; } + // Drive any animations from C++. + // There is a race condition here between getting/setting + // `mDriveCxxAnimations` which shouldn't matter; it's safe to call + // the mBinding method, unless mBinding has gone away. + if (mDriveCxxAnimations && mBinding != null) { + mBinding.driveCxxAnimations(); + } + try { dispatchPreMountItems(frameTimeNanos); tryDispatchMountItems(); - } catch (Exception ex) { FLog.e(TAG, "Exception thrown when executing UIFrameGuarded", ex); stop(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK index 83d5ba3a4fb4fc..7fc222b4702f8c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/BUCK @@ -18,7 +18,7 @@ rn_xplat_cxx_library( "-frtti", ], fbandroid_allow_jni_merging = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", @@ -29,6 +29,7 @@ rn_xplat_cxx_library( deps = [ react_native_xplat_target("better:better"), react_native_xplat_target("config:config"), + react_native_xplat_target("fabric/animations:animations"), react_native_xplat_target("fabric/uimanager:uimanager"), react_native_xplat_target("fabric/scheduler:scheduler"), react_native_xplat_target("fabric/componentregistry:componentregistry"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index e188e9d336e688..025d035502e46f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,10 @@ std::shared_ptr Binding::getScheduler() { return scheduler_; } +LayoutAnimationDriver *Binding::getAnimationDriver() { + return (animationDriver_ ? animationDriver_.get() : nullptr); +} + void Binding::startSurface( jint surfaceId, jni::alias_ref moduleName, @@ -91,7 +96,8 @@ void Binding::startSurface( moduleName->toStdString(), initialProps->consume(), {}, - context); + context, + getAnimationDriver()); } void Binding::startSurfaceWithConstraints( @@ -106,8 +112,11 @@ void Binding::startSurfaceWithConstraints( jboolean doLeftAndRightSwapInRTL) { SystraceSection s("FabricUIManagerBinding::startSurfaceWithConstraints"); - LOG(WARNING) << "Binding::startSurfaceWithConstraints() was called (address: " - << this << ", surfaceId: " << surfaceId << ")."; + if (enableFabricLogs_) { + LOG(WARNING) + << "Binding::startSurfaceWithConstraints() was called (address: " + << this << ", surfaceId: " << surfaceId << ")."; + } std::shared_ptr scheduler = getScheduler(); if (!scheduler) { @@ -134,7 +143,8 @@ void Binding::startSurfaceWithConstraints( moduleName->toStdString(), initialProps->consume(), constraints, - context); + context, + getAnimationDriver()); } void Binding::renderTemplateToSurface(jint surfaceId, jstring uiTemplate) { @@ -155,8 +165,10 @@ void Binding::renderTemplateToSurface(jint surfaceId, jstring uiTemplate) { void Binding::stopSurface(jint surfaceId) { SystraceSection s("FabricUIManagerBinding::stopSurface"); - LOG(WARNING) << "Binding::stopSurface() was called (address: " << this - << ", surfaceId: " << surfaceId << ")."; + if (enableFabricLogs_) { + LOG(WARNING) << "Binding::stopSurface() was called (address: " << this + << ", surfaceId: " << surfaceId << ")."; + } std::shared_ptr scheduler = getScheduler(); if (!scheduler) { @@ -209,8 +221,16 @@ void Binding::installFabricUIManager( jni::alias_ref reactNativeConfig) { SystraceSection s("FabricUIManagerBinding::installFabricUIManager"); - LOG(WARNING) << "Binding::installFabricUIManager() was called (address: " - << this << ")."; + std::shared_ptr config = + std::make_shared(reactNativeConfig); + + enableFabricLogs_ = + config->getBool("react_fabric:enabled_android_fabric_logs"); + + if (enableFabricLogs_) { + LOG(WARNING) << "Binding::installFabricUIManager() was called (address: " + << this << ")."; + } // Use std::lock and std::adopt_lock to prevent deadlocks by locking mutexes // at the same time @@ -253,8 +273,6 @@ void Binding::installFabricUIManager( ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); }; - std::shared_ptr config = - std::make_shared(reactNativeConfig); contextContainer->insert("ReactNativeConfig", config); contextContainer->insert("FabricUIManager", javaUIManager_); @@ -265,14 +283,11 @@ void Binding::installFabricUIManager( collapseDeleteCreateMountingInstructions_ = reactNativeConfig_->getBool( "react_fabric:enabled_collapse_delete_create_mounting_instructions"); - disableVirtualNodePreallocation_ = reactNativeConfig_->getBool( - "react_fabric:disable_virtual_node_preallocation"); - disablePreallocateViews_ = reactNativeConfig_->getBool( "react_fabric:disabled_view_preallocation_android"); - enableOptimizedMovesDiffer_ = reactNativeConfig_->getBool( - "react_fabric:enabled_optimized_moves_differ_android"); + bool enableLayoutAnimations_ = reactNativeConfig_->getBool( + "react_fabric:enabled_layout_animations_android"); auto toolbox = SchedulerToolbox{}; toolbox.contextContainer = contextContainer; @@ -280,12 +295,18 @@ void Binding::installFabricUIManager( toolbox.runtimeExecutor = runtimeExecutor; toolbox.synchronousEventBeatFactory = synchronousBeatFactory; toolbox.asynchronousEventBeatFactory = asynchronousBeatFactory; - scheduler_ = std::make_shared(toolbox, this); + + if (enableLayoutAnimations_) { + animationDriver_ = std::make_unique(this); + } + scheduler_ = std::make_shared(toolbox, getAnimationDriver(), this); } void Binding::uninstallFabricUIManager() { - LOG(WARNING) << "Binding::uninstallFabricUIManager() was called (address: " - << this << ")."; + if (enableFabricLogs_) { + LOG(WARNING) << "Binding::uninstallFabricUIManager() was called (address: " + << this << ")."; + } // Use std::lock and std::adopt_lock to prevent deadlocks by locking mutexes // at the same time std::lock(schedulerMutex_, javaUIManagerMutex_); @@ -583,9 +604,7 @@ void Binding::schedulerDidFinishTransaction( return; } - auto mountingTransaction = mountingCoordinator->pullTransaction( - enableOptimizedMovesDiffer_ ? DifferentiatorMode::OptimizedMoves - : DifferentiatorMode::Classic); + auto mountingTransaction = mountingCoordinator->pullTransaction(); if (!mountingTransaction.has_value()) { return; @@ -862,6 +881,37 @@ void Binding::setPixelDensity(float pointScaleFactor) { pointScaleFactor_ = pointScaleFactor; } +void Binding::onAnimationStarted() { + jni::global_ref localJavaUIManager = getJavaUIManager(); + if (!localJavaUIManager) { + LOG(ERROR) << "Binding::animationsStarted: JavaUIManager disappeared"; + return; + } + + static auto layoutAnimationsStartedJNI = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod("onAnimationStarted"); + + layoutAnimationsStartedJNI(localJavaUIManager); +} +void Binding::onAllAnimationsComplete() { + jni::global_ref localJavaUIManager = getJavaUIManager(); + if (!localJavaUIManager) { + LOG(ERROR) << "Binding::allAnimationsComplete: JavaUIManager disappeared"; + return; + } + + static auto allAnimationsCompleteJNI = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod("onAllAnimationsComplete"); + + allAnimationsCompleteJNI(localJavaUIManager); +} + +void Binding::driveCxxAnimations() { + scheduler_->animationTick(); +} + void Binding::schedulerDidRequestPreliminaryViewAllocation( const SurfaceId surfaceId, const ShadowView &shadowView) { @@ -986,6 +1036,7 @@ void Binding::registerNatives() { makeNativeMethod("stopSurface", Binding::stopSurface), makeNativeMethod("setConstraints", Binding::setConstraints), makeNativeMethod("setPixelDensity", Binding::setPixelDensity), + makeNativeMethod("driveCxxAnimations", Binding::driveCxxAnimations), makeNativeMethod( "uninstallFabricUIManager", Binding::uninstallFabricUIManager)}); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h index 2d7128d23875d1..504fc83b252074 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h @@ -8,10 +8,12 @@ #pragma once #include +#include #include #include #include #include +#include #include #include #include "ComponentFactoryDelegate.h" @@ -22,7 +24,9 @@ namespace react { class Instance; -class Binding : public jni::HybridClass, public SchedulerDelegate { +class Binding : public jni::HybridClass, + public SchedulerDelegate, + public LayoutAnimationStatusDelegate { public: constexpr static const char *const kJavaDescriptor = "Lcom/facebook/react/fabric/Binding;"; @@ -73,26 +77,28 @@ class Binding : public jni::HybridClass, public SchedulerDelegate { void stopSurface(jint surfaceId); void schedulerDidFinishTransaction( - MountingCoordinator::Shared const &mountingCoordinator); + MountingCoordinator::Shared const &mountingCoordinator) override; void schedulerDidRequestPreliminaryViewAllocation( const SurfaceId surfaceId, - const ShadowView &shadowView); + const ShadowView &shadowView) override; void schedulerDidDispatchCommand( const ShadowView &shadowView, std::string const &commandName, - folly::dynamic const args); - - void setPixelDensity(float pointScaleFactor); + folly::dynamic const args) override; void schedulerDidSetJSResponder( SurfaceId surfaceId, const ShadowView &shadowView, const ShadowView &initialShadowView, - bool blockNativeResponder); + bool blockNativeResponder) override; + + void schedulerDidClearJSResponder() override; - void schedulerDidClearJSResponder(); + void setPixelDensity(float pointScaleFactor); + + void driveCxxAnimations(); void uninstallFabricUIManager(); @@ -100,6 +106,12 @@ class Binding : public jni::HybridClass, public SchedulerDelegate { jni::global_ref javaUIManager_; std::mutex javaUIManagerMutex_; + // LayoutAnimations + virtual void onAnimationStarted() override; + virtual void onAllAnimationsComplete() override; + LayoutAnimationDriver *getAnimationDriver(); + std::unique_ptr animationDriver_; + std::shared_ptr scheduler_; std::mutex schedulerMutex_; @@ -112,7 +124,7 @@ class Binding : public jni::HybridClass, public SchedulerDelegate { bool collapseDeleteCreateMountingInstructions_{false}; bool disablePreallocateViews_{false}; bool disableVirtualNodePreallocation_{false}; - bool enableOptimizedMovesDiffer_{false}; + bool enableFabricLogs_{false}; }; } // namespace react diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java index 79966769fd79d7..e48192e8963119 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java @@ -138,7 +138,7 @@ public void addViewAt(int parentTag, int tag, int index) { private @NonNull ViewState getViewState(int tag) { ViewState viewState = mTagToViewState.get(tag); if (viewState == null) { - throw new IllegalStateException("Unable to find viewState view for tag " + tag); + throw new RetryableMountingLayerException("Unable to find viewState view for tag " + tag); } return viewState; } @@ -202,11 +202,13 @@ public void sendAccessibilityEvent(int reactTag, int eventType) { ViewState viewState = getViewState(reactTag); if (viewState.mViewManager == null) { - throw new IllegalStateException("Unable to find viewState manager for tag " + reactTag); + throw new RetryableMountingLayerException( + "Unable to find viewState manager for tag " + reactTag); } if (viewState.mView == null) { - throw new IllegalStateException("Unable to find viewState view for tag " + reactTag); + throw new RetryableMountingLayerException( + "Unable to find viewState view for tag " + reactTag); } viewState.mView.sendAccessibilityEvent(eventType); @@ -240,7 +242,38 @@ public void removeViewAt(int parentTag, int index) { throw new IllegalStateException("Unable to find view for tag " + parentTag); } - getViewGroupManager(viewState).removeViewAt(parentView, index); + ViewGroupManager viewGroupManager = getViewGroupManager(viewState); + + try { + viewGroupManager.removeViewAt(parentView, index); + } catch (RuntimeException e) { + // Note: `getChildCount` may not always be accurate! + // We don't currently have a good explanation other than, in situations where you + // would empirically expect to see childCount > 0, the childCount is reported as 0. + // This is likely due to a ViewManager overriding getChildCount or some other methods + // in a way that is strictly incorrect, but potentially only visible here. + // The failure mode is actually that in `removeViewAt`, a NullPointerException is + // thrown when we try to perform an operation on a View that doesn't exist, and + // is therefore null. + // We try to add some extra diagnostics here, but we always try to remove the View + // from the hierarchy first because detecting by looking at childCount will not work. + // + // Note that the lesson here is that `getChildCount` is not /required/ to adhere to + // any invariants. If you add 9 children to a parent, the `getChildCount` of the parent + // may not be equal to 9. This apparently causes no issues with Android and is common + // enough that we shouldn't try to change this invariant, without a lot of thought. + int childCount = viewGroupManager.getChildCount(parentView); + + throw new IllegalStateException( + "Cannot remove child at index " + + index + + " from parent ViewGroup [" + + parentView.getId() + + "], only " + + childCount + + " children in parent. Warning: childCount may be incorrect!", + e); + } } @UiThread @@ -371,37 +404,6 @@ public void deleteView(int reactTag) { } } - @UiThread - public void updateLocalData(int reactTag, @NonNull ReadableMap newLocalData) { - UiThreadUtil.assertOnUiThread(); - ViewState viewState = getViewState(reactTag); - if (viewState.mCurrentProps == null) { - throw new IllegalStateException( - "Can not update local data to view without props: " + reactTag); - } - if (viewState.mCurrentLocalData != null - && newLocalData.hasKey("hash") - && viewState.mCurrentLocalData.getDouble("hash") == newLocalData.getDouble("hash") - && viewState.mCurrentLocalData.equals(newLocalData)) { - return; - } - viewState.mCurrentLocalData = newLocalData; - - ViewManager viewManager = viewState.mViewManager; - - if (viewManager == null) { - throw new IllegalStateException("Unable to find ViewManager for view: " + viewState); - } - Object extraData = - viewManager.updateLocalData( - viewState.mView, - viewState.mCurrentProps, - new ReactStylesDiffMap(viewState.mCurrentLocalData)); - if (extraData != null) { - viewManager.updateExtraData(viewState.mView, extraData); - } - } - @UiThread public void updateState(final int reactTag, @Nullable StateWrapper stateWrapper) { UiThreadUtil.assertOnUiThread(); @@ -519,7 +521,7 @@ public long measure( @NonNull YogaMeasureMode widthMode, float height, @NonNull YogaMeasureMode heightMode, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { return mViewManagerRegistry .get(componentName) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SendAccessibilityEvent.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SendAccessibilityEvent.java index 207d79871ccd5e..5852ff1a78c643 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SendAccessibilityEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SendAccessibilityEvent.java @@ -8,10 +8,14 @@ package com.facebook.react.fabric.mounting.mountitems; import androidx.annotation.NonNull; +import com.facebook.react.bridge.ReactSoftException; +import com.facebook.react.bridge.RetryableMountingLayerException; import com.facebook.react.fabric.mounting.MountingManager; public class SendAccessibilityEvent implements MountItem { + private final String TAG = "Fabric.SendAccessibilityEvent"; + private final int mReactTag; private final int mEventType; @@ -22,7 +26,18 @@ public SendAccessibilityEvent(int reactTag, int eventType) { @Override public void execute(@NonNull MountingManager mountingManager) { - mountingManager.sendAccessibilityEvent(mReactTag, mEventType); + try { + mountingManager.sendAccessibilityEvent(mReactTag, mEventType); + } catch (RetryableMountingLayerException e) { + // Accessibility events are similar to commands in that they're imperative + // calls from JS, disconnected from the commit lifecycle, and therefore + // inherently unpredictable and dangerous. If we encounter a "retryable" + // error, that is, a known category of errors that this is likely to hit + // due to race conditions (like the view disappearing after the event is + // queued and before it executes), we log a soft exception and continue along. + // Other categories of errors will still cause a hard crash. + ReactSoftException.logSoftException(TAG, e); + } } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java deleted file mode 100644 index 07104a3f5294b2..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.fabric.mounting.mountitems; - -import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT; - -import androidx.annotation.NonNull; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.fabric.mounting.MountingManager; - -public class UpdateLocalDataMountItem implements MountItem { - - private final int mReactTag; - @NonNull private final ReadableMap mNewLocalData; - - public UpdateLocalDataMountItem(int reactTag, @NonNull ReadableMap newLocalData) { - mReactTag = reactTag; - mNewLocalData = newLocalData; - } - - @Override - public void execute(@NonNull MountingManager mountingManager) { - mountingManager.updateLocalData(mReactTag, mNewLocalData); - } - - public @NonNull ReadableMap getNewLocalData() { - return mNewLocalData; - } - - @Override - public String toString() { - StringBuilder result = - new StringBuilder("UpdateLocalDataMountItem [").append(mReactTag).append("]"); - - if (IS_DEVELOPMENT_ENVIRONMENT) { - result.append(" localData: ").append(mNewLocalData); - } - - return result.toString(); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/BUCK b/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/BUCK index 1f1f74690a4719..a1a374ef936b36 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_nat rn_android_library( name = "jscexecutor", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/jstasks/BUCK b/ReactAndroid/src/main/java/com/facebook/react/jstasks/BUCK index b2cdc99110b575..26444912bf4e73 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/jstasks/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/jstasks/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "jstasks", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK index f081fc83a17774..b213c9d3133a0f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/module/annotations/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "annotations", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK b/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK index 59e700971e872b..85f23a35691b69 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/module/model/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_libra rn_android_library( name = "model", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK b/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK index b2c8041bdc6981..cc44f729d1893a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/module/processing/BUCK @@ -15,7 +15,7 @@ rn_java_annotation_processor( rn_java_library( name = "processing-lib", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], source = "8", target = "8", deps = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/BUCK index 5d0fa7d9d1c958..f12d0bf3c4b440 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "accessibilityinfo", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/BUCK index 62802072e94797..ef3d7751982482 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "appearance", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appregistry/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/appregistry/BUCK index dc2c4d824b2a52..d54b2cd82034de 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/appregistry/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appregistry/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_target", "rn_android_li rn_android_library( name = "appregistry", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK index 3b873f61574438..92c4684d3014fe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "appstate", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BUCK index 0a4d2b472e2210..1831ff654368cb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "blob", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/blob/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/blob/jni/BUCK index 2ab5488c94a15f..01a57b64d3e326 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/blob/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/blob/jni/BUCK @@ -11,7 +11,7 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = ANDROID, soname = "libreactnativeblob.$(ext)", visibility = ["PUBLIC"], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK index 01e0240e5b7dee..13384e7ac49b3d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "camera", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK index c3fa4b7884140b..d755fa2dd9aa55 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "clipboard", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK index c8376f61beec51..47dad24dd0491a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "common", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK index 4e8a8ee3fafff2..535ee65d070cc3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "core", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK index 91683c6f233033..f4b8776d0c113e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "datepicker", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK index 2f0d2caef1a471..389c2d9681cef3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "debug", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], @@ -30,7 +30,7 @@ rn_android_library( rn_android_library( name = "interfaces", srcs = glob(["interfaces/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/BUCK index a33ecbf2677641..65a9d8d0bb2aca 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "deviceinfo", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK index 1987e6e9223e16..b6a6e85c354448 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "dialog", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fabric/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/fabric/BUCK index 5bbb74d994b22f..f88d3d22465bda 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fabric/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fabric/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_target", "rn_android_li rn_android_library( name = "fabric", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ ], visibility = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK index fd6819910638ce..a0762971a030ed 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "fresco", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK index a00c306f8da8a4..2b7eeb854d9425 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "i18nmanager", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK index d924754e990d0e..706425f1bc3e8e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "image", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK index a7f538cc2ea321..e3102ed4685ca2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "intent", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK index ccb9013abe9045..088baa4883eb36 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "network", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/BUCK index 484e374c72a94c..4943b35a537b3f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/permissions/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "permissions", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK index 11288aa404c14c..8bcc886c1b0652 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/share/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "share", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/sound/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/sound/BUCK index 8e199ed82a85e2..aefbb42597bfe6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/sound/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/sound/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "sound", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ ], visibility = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK index 8643e041815f6a..835340443bbd44 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "statusbar", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK index 2231b08feec476..716282e68cfcba 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "storage", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK index afc48e3db35129..89a47d4f0d2ea6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK @@ -7,7 +7,7 @@ rn_android_library( "ReactNativeVersion.java", ], is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], @@ -32,7 +32,7 @@ rn_android_library( "AndroidInfoHelpers.java", ], is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/BUCK deleted file mode 100644 index dac030819debc6..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/BUCK +++ /dev/null @@ -1,26 +0,0 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") - -rn_android_library( - name = "timepicker", - srcs = glob(["**/*.java"]), - is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], - provided_deps = [ - react_native_dep("third-party/android/androidx:annotation"), - react_native_dep("third-party/android/androidx:core"), - react_native_dep("third-party/android/androidx:fragment"), - react_native_dep("third-party/android/androidx:legacy-support-core-ui"), - react_native_dep("third-party/android/androidx:legacy-support-core-utils"), - ], - visibility = [ - "PUBLIC", - ], - deps = [ - react_native_dep("third-party/java/infer-annotations:infer-annotations"), - react_native_dep("third-party/java/jsr-305:jsr-305"), - react_native_target("java/com/facebook/react/bridge:bridge"), - react_native_target("java/com/facebook/react/common:common"), - react_native_target("java/com/facebook/react/module/annotations:annotations"), - ], - exported_deps = [react_native_target("java/com/facebook/fbreact/specs:FBReactNativeSpec")], -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/DismissableTimePickerDialog.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/DismissableTimePickerDialog.java deleted file mode 100644 index 23540757dea757..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/DismissableTimePickerDialog.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.modules.timepicker; - -import android.app.TimePickerDialog; -import android.content.Context; -import android.os.Build; -import androidx.annotation.Nullable; - -/** - * Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the {@link - * TimePickerDialog} still calls the OnTimeSetListener. This class works around that issue by *not* - * calling super.onStop on KitKat on lower, as that would erroneously call the OnTimeSetListener - * when the dialog is dismissed, or call it twice when "OK" is pressed. - * - *

See: Issue 34833 - */ -public class DismissableTimePickerDialog extends TimePickerDialog { - - public DismissableTimePickerDialog( - Context context, - @Nullable TimePickerDialog.OnTimeSetListener callback, - int hourOfDay, - int minute, - boolean is24HourView) { - super(context, callback, hourOfDay, minute, is24HourView); - } - - public DismissableTimePickerDialog( - Context context, - int theme, - @Nullable TimePickerDialog.OnTimeSetListener callback, - int hourOfDay, - int minute, - boolean is24HourView) { - super(context, theme, callback, hourOfDay, minute, is24HourView); - } - - @Override - protected void onStop() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - super.onStop(); - } - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogFragment.java deleted file mode 100644 index 298e0bc14c2040..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogFragment.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.modules.timepicker; - -import android.app.Dialog; -import android.app.TimePickerDialog.OnTimeSetListener; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; -import android.os.Build; -import android.os.Bundle; -import android.text.format.DateFormat; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import java.util.Calendar; -import java.util.Locale; - -@SuppressWarnings("ValidFragment") -public class TimePickerDialogFragment extends DialogFragment { - - @Nullable private OnTimeSetListener mOnTimeSetListener; - @Nullable private OnDismissListener mOnDismissListener; - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Bundle args = getArguments(); - return createDialog(args, getActivity(), mOnTimeSetListener); - } - - /*package*/ static Dialog createDialog( - Bundle args, Context activityContext, @Nullable OnTimeSetListener onTimeSetListener) { - final Calendar now = Calendar.getInstance(); - int hour = now.get(Calendar.HOUR_OF_DAY); - int minute = now.get(Calendar.MINUTE); - boolean is24hour = DateFormat.is24HourFormat(activityContext); - - TimePickerMode mode = TimePickerMode.DEFAULT; - if (args != null && args.getString(TimePickerDialogModule.ARG_MODE, null) != null) { - mode = - TimePickerMode.valueOf( - args.getString(TimePickerDialogModule.ARG_MODE).toUpperCase(Locale.US)); - } - - if (args != null) { - hour = args.getInt(TimePickerDialogModule.ARG_HOUR, now.get(Calendar.HOUR_OF_DAY)); - minute = args.getInt(TimePickerDialogModule.ARG_MINUTE, now.get(Calendar.MINUTE)); - is24hour = - args.getBoolean( - TimePickerDialogModule.ARG_IS24HOUR, DateFormat.is24HourFormat(activityContext)); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (mode == TimePickerMode.CLOCK) { - return new DismissableTimePickerDialog( - activityContext, - activityContext - .getResources() - .getIdentifier("ClockTimePickerDialog", "style", activityContext.getPackageName()), - onTimeSetListener, - hour, - minute, - is24hour); - } else if (mode == TimePickerMode.SPINNER) { - return new DismissableTimePickerDialog( - activityContext, - activityContext - .getResources() - .getIdentifier( - "SpinnerTimePickerDialog", "style", activityContext.getPackageName()), - onTimeSetListener, - hour, - minute, - is24hour); - } - } - return new DismissableTimePickerDialog( - activityContext, onTimeSetListener, hour, minute, is24hour); - } - - @Override - public void onDismiss(DialogInterface dialog) { - super.onDismiss(dialog); - if (mOnDismissListener != null) { - mOnDismissListener.onDismiss(dialog); - } - } - - public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) { - mOnDismissListener = onDismissListener; - } - - public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) { - mOnTimeSetListener = onTimeSetListener; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogModule.java deleted file mode 100644 index b40f7b6b46c6bc..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogModule.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.modules.timepicker; - -import android.app.Activity; -import android.app.TimePickerDialog.OnTimeSetListener; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; -import android.os.Bundle; -import android.widget.TimePicker; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import com.facebook.fbreact.specs.NativeTimePickerAndroidSpec; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeMap; -import com.facebook.react.common.annotations.VisibleForTesting; -import com.facebook.react.module.annotations.ReactModule; - -/** - * {@link NativeModule} that allows JS to show a native time picker dialog and get called back when - * the user selects a time. - */ -@ReactModule(name = TimePickerDialogModule.FRAGMENT_TAG) -public class TimePickerDialogModule extends NativeTimePickerAndroidSpec { - - @VisibleForTesting public static final String FRAGMENT_TAG = "TimePickerAndroid"; - - private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY"; - - /* package */ static final String ARG_HOUR = "hour"; - /* package */ static final String ARG_MINUTE = "minute"; - /* package */ static final String ARG_IS24HOUR = "is24Hour"; - /* package */ static final String ARG_MODE = "mode"; - /* package */ static final String ACTION_TIME_SET = "timeSetAction"; - /* package */ static final String ACTION_DISMISSED = "dismissedAction"; - - public TimePickerDialogModule(ReactApplicationContext reactContext) { - super(reactContext); - } - - @Override - public String getName() { - return FRAGMENT_TAG; - } - - private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener { - - private final Promise mPromise; - private boolean mPromiseResolved = false; - - public TimePickerDialogListener(Promise promise) { - mPromise = promise; - } - - @Override - public void onTimeSet(TimePicker view, int hour, int minute) { - if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) { - WritableMap result = new WritableNativeMap(); - result.putString("action", ACTION_TIME_SET); - result.putInt("hour", hour); - result.putInt("minute", minute); - mPromise.resolve(result); - mPromiseResolved = true; - } - } - - @Override - public void onDismiss(DialogInterface dialog) { - if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) { - WritableMap result = new WritableNativeMap(); - result.putString("action", ACTION_DISMISSED); - mPromise.resolve(result); - mPromiseResolved = true; - } - } - } - - @Override - public void open(@Nullable final ReadableMap options, Promise promise) { - - Activity raw_activity = getCurrentActivity(); - if (raw_activity == null || !(raw_activity instanceof FragmentActivity)) { - promise.reject( - ERROR_NO_ACTIVITY, - "Tried to open a DatePicker dialog while not attached to a FragmentActivity"); - return; - } - - FragmentActivity activity = (FragmentActivity) raw_activity; - - // We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity - // (for apps that use it for legacy reasons). This unfortunately leads to some code duplication. - FragmentManager fragmentManager = activity.getSupportFragmentManager(); - DialogFragment oldFragment = (DialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); - if (oldFragment != null) { - oldFragment.dismiss(); - } - TimePickerDialogFragment fragment = new TimePickerDialogFragment(); - if (options != null) { - Bundle args = createFragmentArguments(options); - fragment.setArguments(args); - } - TimePickerDialogListener listener = new TimePickerDialogListener(promise); - fragment.setOnDismissListener(listener); - fragment.setOnTimeSetListener(listener); - fragment.show(fragmentManager, FRAGMENT_TAG); - } - - private Bundle createFragmentArguments(ReadableMap options) { - final Bundle args = new Bundle(); - if (options.hasKey(ARG_HOUR) && !options.isNull(ARG_HOUR)) { - args.putInt(ARG_HOUR, options.getInt(ARG_HOUR)); - } - if (options.hasKey(ARG_MINUTE) && !options.isNull(ARG_MINUTE)) { - args.putInt(ARG_MINUTE, options.getInt(ARG_MINUTE)); - } - if (options.hasKey(ARG_IS24HOUR) && !options.isNull(ARG_IS24HOUR)) { - args.putBoolean(ARG_IS24HOUR, options.getBoolean(ARG_IS24HOUR)); - } - if (options.hasKey(ARG_MODE) && !options.isNull(ARG_MODE)) { - args.putString(ARG_MODE, options.getString(ARG_MODE)); - } - return args; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK index fb3b79960fe585..9cd1eaa53d005c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "toast", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK index 219a1d36feb288..69081110ea8600 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "vibration", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK index 4299354a4a6d76..d80e8fd4d06646 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "websocket", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK b/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK index 1cfd88bfc35a01..3aa924ecad3ad6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK @@ -6,7 +6,7 @@ rn_android_library( ["**/*.java"], ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK b/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK index 5ff37878c58a18..c31511049ce9ea 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK @@ -15,7 +15,7 @@ rn_java_annotation_processor( rn_java_library( name = "processing-lib", srcs = glob(["*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], source = "7", target = "7", deps = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK index 5f114f91e88e13..8c538aa0e96aef 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "shell", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), @@ -46,7 +46,6 @@ rn_android_library( react_native_target("java/com/facebook/react/modules/statusbar:statusbar"), react_native_target("java/com/facebook/react/modules/storage:storage"), react_native_target("java/com/facebook/react/modules/sound:sound"), - react_native_target("java/com/facebook/react/modules/timepicker:timepicker"), react_native_target("java/com/facebook/react/modules/toast:toast"), react_native_target("java/com/facebook/react/modules/vibration:vibration"), react_native_target("java/com/facebook/react/modules/websocket:websocket"), @@ -65,7 +64,6 @@ rn_android_library( react_native_target("java/com/facebook/react/views/text/frescosupport:frescosupport"), react_native_target("java/com/facebook/react/views/textinput:textinput"), react_native_target("java/com/facebook/react/views/view:view"), - react_native_target("java/com/facebook/react/views/viewpager:viewpager"), react_native_target("java/com/facebook/react/module/annotations:annotations"), react_native_target("res:shell"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index e5d6f99a3186b9..3cd6defd986b28 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -37,7 +37,6 @@ import com.facebook.react.modules.sound.SoundManagerModule; import com.facebook.react.modules.statusbar.StatusBarModule; import com.facebook.react.modules.storage.AsyncStorageModule; -import com.facebook.react.modules.timepicker.TimePickerDialogModule; import com.facebook.react.modules.toast.ToastModule; import com.facebook.react.modules.vibration.VibrationModule; import com.facebook.react.modules.websocket.WebSocketModule; @@ -61,7 +60,6 @@ import com.facebook.react.views.text.frescosupport.FrescoBasedReactTextInlineImageViewManager; import com.facebook.react.views.textinput.ReactTextInputManager; import com.facebook.react.views.view.ReactViewManager; -import com.facebook.react.views.viewpager.ReactViewPagerManager; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -92,7 +90,6 @@ ShareModule.class, SoundManagerModule.class, StatusBarModule.class, - TimePickerDialogModule.class, ToastModule.class, VibrationModule.class, WebSocketModule.class, @@ -155,8 +152,6 @@ public MainReactPackage(MainPackageConfig config) { return new StatusBarModule(context); case SoundManagerModule.NAME: return new SoundManagerModule(context); - case TimePickerDialogModule.FRAGMENT_TAG: - return new TimePickerDialogModule(context); case ToastModule.NAME: return new ToastModule(context); case VibrationModule.NAME: @@ -191,7 +186,6 @@ public List createViewManagers(ReactApplicationContext reactContext viewManagers.add(new ReactTextInputManager()); viewManagers.add(new ReactTextViewManager()); viewManagers.add(new ReactViewManager()); - viewManagers.add(new ReactViewPagerManager()); viewManagers.add(new ReactVirtualTextViewManager()); return viewManagers; @@ -229,7 +223,6 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { ShareModule.class, StatusBarModule.class, SoundManagerModule.class, - TimePickerDialogModule.class, ToastModule.class, VibrationModule.class, WebSocketModule.class diff --git a/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK b/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK index 7ea360411204d6..beb35ce40c1055 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "surface", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK b/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK index c57d2e682f350d..839bd5beb44bb0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "touch", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK index 7547027a771a8d..50e716a39a8cb6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK @@ -8,7 +8,7 @@ rn_android_library( ], exclude = ["CallInvokerHolderImpl.java"], ), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", @@ -39,7 +39,7 @@ rn_android_library( rn_android_library( name = "callinvokerholder", srcs = ["CallInvokerHolderImpl.java"], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/BUCK b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/BUCK index 3e07e5c5355a80..ad97b93f20d94a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/BUCK @@ -6,7 +6,7 @@ rn_android_library( ["*.java"], ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK index 183194f3d47dd9..9a7eb46af4bf50 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK @@ -17,7 +17,7 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = ANDROID, preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", @@ -56,7 +56,7 @@ rn_xplat_cxx_library( fbandroid_deps = [ FBJNI_TARGET, ], - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = ANDROID, preferred_linkage = "static", preprocessor_flags = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK index a88d679b87f4f3..09e0e6ea4ec460 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK @@ -14,7 +14,7 @@ rn_android_library( ], ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), @@ -60,7 +60,7 @@ rn_android_library( "DisplayMetricsHolder.java", ], is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java index b1c531f8f607f5..f430398bacf208 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java @@ -113,4 +113,7 @@ public void setProperty(T view, String propName, @Nullable Object value) { break; } } + + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) {} } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/MatrixMathHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/MatrixMathHelper.java index 179cc32e6a1842..cf448bd58908d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/MatrixMathHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/MatrixMathHelper.java @@ -169,10 +169,6 @@ public static void decomposeMatrix(double[] transformMatrix, MatrixDecomposition skew[0] = v3Dot(row[0], row[1]); row[1] = v3Combine(row[1], row[0], 1.0, -skew[0]); - // Compute XY shear factor and make 2nd row orthogonal to 1st. - skew[0] = v3Dot(row[0], row[1]); - row[1] = v3Combine(row[1], row[0], 1.0, -skew[0]); - // Now, compute Y scale and normalize 2nd row. scale[1] = v3Length(row[1]); row[1] = v3Normalize(row[1], scale[1]); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 35c9ff92013033..749202eeaad64a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -29,6 +29,7 @@ import com.facebook.react.bridge.RetryableMountingLayerException; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.touch.JSResponderHandler; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationListener; @@ -784,7 +785,13 @@ public synchronized void dispatchCommand( + commandId); } ViewManager viewManager = resolveViewManager(reactTag); - viewManager.receiveCommand(view, commandId, args); + ViewManagerDelegate delegate; + if (ReactFeatureFlags.useViewManagerDelegatesForCommands + && (delegate = viewManager.getDelegate()) != null) { + delegate.receiveCommand(view, commandId, args); + } else { + viewManager.receiveCommand(view, commandId, args); + } } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index fc13de9c5ccef7..c21eaecad3630a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -288,6 +288,8 @@ public interface ReactShadowNode { YogaValue getStyleHeight(); + float getFlex(); + void setStyleHeight(float heightPx); void setStyleHeightPercent(float percent); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java index 6c58ae2d7b93d1..18aaad08112ad5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -779,6 +779,11 @@ public void setStyleMaxHeightPercent(float percent) { mYogaNode.setMaxHeightPercent(percent); } + @Override + public float getFlex() { + return mYogaNode.getFlex(); + } + @Override public void setFlex(float flex) { mYogaNode.setFlex(flex); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java index 4eff054efe9c3b..039798dcaf321f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java @@ -10,8 +10,6 @@ import android.app.Activity; import android.content.Context; import androidx.annotation.Nullable; -import com.facebook.react.bridge.JSIModule; -import com.facebook.react.bridge.JSIModuleType; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; @@ -71,16 +69,12 @@ public boolean hasCurrentActivity() { return mSurfaceID; } - @Override - public boolean isBridgeless() { - return mReactApplicationContext.isBridgeless(); + public ReactApplicationContext getReactApplicationContext() { + return mReactApplicationContext; } @Override - public JSIModule getJSIModule(JSIModuleType moduleType) { - if (isBridgeless()) { - return mReactApplicationContext.getJSIModule(moduleType); - } - return super.getJSIModule(moduleType); + public boolean isBridgeless() { + return mReactApplicationContext.isBridgeless(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java index 16545dd1eac9fe..e7f790b0570485 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.java @@ -48,6 +48,17 @@ public static void processTransform(ReadableArray transforms, double[] result) { double[] helperMatrix = sHelperMatrix.get(); MatrixMathHelper.resetIdentityMatrix(result); + // If the transforms array is actually just the matrix itself, + // copy that directly. This is for Fabric LayoutAnimations support. + // All of the stuff this Java helper does is already done in C++ in Fabric, so we + // can just use that matrix directly. + if (transforms.size() == 16 && transforms.getType(0) == ReadableType.Number) { + for (int i = 0; i < transforms.size(); i++) { + result[i] = transforms.getDouble(i); + } + return; + } + for (int transformIdx = 0, size = transforms.size(); transformIdx < size; transformIdx++) { ReadableMap transform = transforms.getMap(transformIdx); String transformType = transform.keySetIterator().nextKey(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java index c034e06830b069..bd978336c2d0c8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java @@ -22,6 +22,7 @@ import com.facebook.react.bridge.UIManager; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.EventDispatcherProvider; /** Helper class for {@link UIManager}. */ public class UIManagerHelper { @@ -43,32 +44,28 @@ private static UIManager getUIManager( ReactContext context, @UIManagerType int uiManagerType, boolean returnNullIfCatalystIsInactive) { - if (context.isBridgeless()) { - return (UIManager) context.getJSIModule(JSIModuleType.UIManager); - } else { - if (!context.hasCatalystInstance()) { - ReactSoftException.logSoftException( - "UIManagerHelper", - new ReactNoCrashSoftException( - "Cannot get UIManager because the context doesn't contain a CatalystInstance.")); + if (!context.hasCatalystInstance()) { + ReactSoftException.logSoftException( + "UIManagerHelper", + new ReactNoCrashSoftException( + "Cannot get UIManager because the context doesn't contain a CatalystInstance.")); + return null; + } + // TODO T60461551: add tests to verify emission of events when the ReactContext is being turn + // down. + if (!context.hasActiveCatalystInstance()) { + ReactSoftException.logSoftException( + "UIManagerHelper", + new ReactNoCrashSoftException( + "Cannot get UIManager because the context doesn't contain an active CatalystInstance.")); + if (returnNullIfCatalystIsInactive) { return null; } - // TODO T60461551: add tests to verify emission of events when the ReactContext is being turn - // down. - if (!context.hasActiveCatalystInstance()) { - ReactSoftException.logSoftException( - "UIManagerHelper", - new ReactNoCrashSoftException( - "Cannot get UIManager because the context doesn't contain an active CatalystInstance.")); - if (returnNullIfCatalystIsInactive) { - return null; - } - } - CatalystInstance catalystInstance = context.getCatalystInstance(); - return uiManagerType == FABRIC - ? (UIManager) catalystInstance.getJSIModule(JSIModuleType.UIManager) - : catalystInstance.getNativeModule(UIManagerModule.class); } + CatalystInstance catalystInstance = context.getCatalystInstance(); + return uiManagerType == FABRIC + ? (UIManager) catalystInstance.getJSIModule(JSIModuleType.UIManager) + : catalystInstance.getNativeModule(UIManagerModule.class); } /** @@ -87,6 +84,13 @@ public static EventDispatcher getEventDispatcherForReactTag(ReactContext context @Nullable public static EventDispatcher getEventDispatcher( ReactContext context, @UIManagerType int uiManagerType) { + // TODO T67518514 Clean this up once we migrate everything over to bridgeless mode + if (context.isBridgeless()) { + if (context instanceof ThemedReactContext) { + context = ((ThemedReactContext) context).getReactApplicationContext(); + } + return ((EventDispatcherProvider) context).getEventDispatcher(); + } UIManager uiManager = getUIManager(context, uiManagerType, false); return uiManager == null ? null : (EventDispatcher) uiManager.getEventDispatcher(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index d753e252a567c4..1f51e272ac9179 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -13,9 +13,7 @@ import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import android.content.ComponentCallbacks2; -import android.content.Context; import android.content.res.Configuration; -import android.media.AudioManager; import android.view.View; import androidx.annotation.Nullable; import androidx.collection.ArrayMap; @@ -36,6 +34,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.MapBuilder; @@ -44,6 +43,7 @@ import com.facebook.react.uimanager.common.ViewUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.EventDispatcherImpl; import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; @@ -119,6 +119,7 @@ public interface CustomEventNamesResolver { private final UIImplementation mUIImplementation; private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback(); private final List mListeners = new ArrayList<>(); + private final List mUIManagerListeners = new ArrayList<>(); private @Nullable Map mViewManagerConstantsCache; private volatile int mViewManagerConstantsCacheSize; @@ -156,7 +157,7 @@ public UIManagerModule( int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); - mEventDispatcher = new EventDispatcher(reactContext); + mEventDispatcher = new EventDispatcherImpl(reactContext); mModuleConstants = createConstants(viewManagerResolver); mCustomDirectEvents = UIManagerModuleConstants.getDirectEventTypeConstants(); mViewManagerRegistry = new ViewManagerRegistry(viewManagerResolver); @@ -178,7 +179,7 @@ public UIManagerModule( int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); - mEventDispatcher = new EventDispatcher(reactContext); + mEventDispatcher = new EventDispatcherImpl(reactContext); mCustomDirectEvents = MapBuilder.newHashMap(); mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents); mViewManagerRegistry = new ViewManagerRegistry(viewManagersList); @@ -285,7 +286,8 @@ private static Map createConstants( * Helper method to pre-compute the constants for a view manager. This method ensures that we * don't block for getting the constants for view managers during TTI * - * @param viewManagerNames + * @deprecated this method will not be available in FabricUIManager class. + * @param viewManagerNames {@link List} names of ViewManagers */ @Deprecated public void preComputeConstantsForViewManager(List viewManagerNames) { @@ -570,7 +572,7 @@ public void setChildren(int viewTag, ReadableArray childrenTags) { * This resolves to a simple {@link #manageChildren} call, but React doesn't have enough info in * JS to formulate it itself. * - * @deprecated This method will not be available in Fabric UIManager. + * @deprecated This method will not be available in Fabric UIManager class. */ @ReactMethod @Deprecated @@ -580,10 +582,10 @@ public void replaceExistingNonRootView(int oldTag, int newTag) { /** * Method which takes a container tag and then releases all subviews for that container upon - * receipt. TODO: The method name is incorrect and will be renamed, #6033872 + * receipt. * * @param containerTag the tag of the container for which the subviews must be removed - * @deprecated This method will not be available in Fabric UIManager. + * @deprecated This method will not be available in Fabric UIManager class. */ @ReactMethod @Deprecated @@ -633,7 +635,7 @@ public void measureLayout( * window which can cause unexpected results when measuring relative to things like ScrollViews * that can have offset content on the screen. * - * @deprecated This method will not be part of Fabric. + * @deprecated this method will not be available in FabricUIManager class. */ @ReactMethod @Deprecated @@ -666,7 +668,7 @@ public void findSubviewIn( /** * Check if the first shadow node is the descendant of the second shadow node * - * @deprecated This method will not be part of Fabric. + * @deprecated this method will not be available in FabricUIManager class. */ @ReactMethod @Deprecated @@ -717,17 +719,6 @@ public void dispatchCommand(int reactTag, String commandId, @Nullable ReadableAr mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs); } - /** @deprecated use {@link SoundManager#playTouchSound()} instead. */ - @ReactMethod - @Deprecated - public void playTouchSound() { - AudioManager audioManager = - (AudioManager) getReactApplicationContext().getSystemService(Context.AUDIO_SERVICE); - if (audioManager != null) { - audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); - } - } - /** * Show a PopupMenu. * @@ -805,6 +796,9 @@ public void onBatchComplete() { for (UIManagerModuleListener listener : mListeners) { listener.willDispatchViewUpdates(this); } + for (UIManagerListener listener : mUIManagerListeners) { + listener.willDispatchViewUpdates(this); + } try { mUIImplementation.dispatchViewUpdates(batchId); } finally { @@ -862,14 +856,24 @@ public void prependUIBlock(UIBlock block) { mUIImplementation.prependUIBlock(block); } + @Deprecated public void addUIManagerListener(UIManagerModuleListener listener) { mListeners.add(listener); } + @Deprecated public void removeUIManagerListener(UIManagerModuleListener listener) { mListeners.remove(listener); } + public void addUIManagerEventListener(UIManagerListener listener) { + mUIManagerListeners.add(listener); + } + + public void removeUIManagerEventListener(UIManagerListener listener) { + mUIManagerListeners.remove(listener); + } + /** * Given a reactTag from a component, find its root node tag, if possible. Otherwise, this will * return 0. If the reactTag belongs to a root node, this will return the same reactTag. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java index 270d0dcf650277..19163fd6c2d66d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java @@ -7,7 +7,11 @@ package com.facebook.react.uimanager; -/** Listener used to hook into the UIManager update process. */ +/** + * Listener used to hook into the UIManager update process. Deprecated: use UIManagerListener + * instead. This will be deleted in some future release. + */ +@Deprecated public interface UIManagerModuleListener { /** * Called right before view updates are dispatched at the end of a batch. This is useful if a diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java index 0447427d3f0d30..cabed90229c86e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java @@ -273,11 +273,6 @@ public Map getNativeProps() { return ViewManagerPropertyUpdater.getNativeProps(getClass(), getShadowNodeClass()); } - public @Nullable Object updateLocalData( - @NonNull T view, ReactStylesDiffMap props, ReactStylesDiffMap localData) { - return null; - } - /** * Subclasses can implement this method to receive state updates shared between all instances of * this component type. @@ -318,7 +313,7 @@ public long measure( YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { return 0; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerDelegate.java index 79193d53dbbaa3..a464a10c97e1c8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerDelegate.java @@ -9,13 +9,17 @@ import android.view.View; import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; /** * This is an interface that must be implemented by classes that wish to take over the - * responsibility of setting properties of all views managed by the view manager. + * responsibility of setting properties of all views managed by the view manager and executing view + * commands. * * @param the type of the view supported by this delegate */ public interface ViewManagerDelegate { void setProperty(T view, String propName, @Nullable Object value); + + void receiveCommand(T view, String commandName, ReadableArray args); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK index 39953cea578b08..11bc3ba33e01ef 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "annotations", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/BUCK index 9f54d4ad220281..d8e9532b1a6a6f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "common", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java new file mode 100644 index 00000000000000..1fce7974087b6a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlackHoleEventDispatcher.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager.events; + +import com.facebook.common.logging.FLog; + +/** + * A singleton class that overrides {@link EventDispatcher} with no-op methods, to be used by + * callers that expect an EventDispatcher when the instance doesn't exist. + */ +public class BlackHoleEventDispatcher implements EventDispatcher { + + private static final EventDispatcher sEventDispatcher = new BlackHoleEventDispatcher(); + + public static EventDispatcher get() { + return sEventDispatcher; + } + + private BlackHoleEventDispatcher() {} + + @Override + public void dispatchEvent(Event event) { + FLog.d( + getClass().getSimpleName(), + "Trying to emit event to JS, but the React instance isn't ready. Event: " + + event.getEventName()); + } + + @Override + public void dispatchAllEvents() {} + + @Override + public void addListener(EventDispatcherListener listener) {} + + @Override + public void removeListener(EventDispatcherListener listener) {} + + @Override + public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) {} + + @Override + public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) {} + + @Override + public void registerEventEmitter(int uiManagerType, RCTEventEmitter eventEmitter) {} + + @Override + public void unregisterEventEmitter(int uiManagerType) {} + + @Override + public void onCatalystInstanceDestroyed() {} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java index 68ed01c9858c54..2ba18340977281 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java @@ -7,383 +7,28 @@ package com.facebook.react.uimanager.events; -import android.util.LongSparseArray; -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.modules.core.ChoreographerCompat; -import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.uimanager.common.UIManagerType; -import com.facebook.systrace.Systrace; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -/** - * Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an - * intermediary between UI code generating events and JS, making sure we don't send more events than - * JS can process. - * - *

To use it, create a subclass of {@link Event} and call {@link #dispatchEvent(Event)} whenever - * there's a UI event to dispatch. - * - *

This class works by installing a Choreographer frame callback on the main thread. This - * callback then enqueues a runnable on the JS thread (if one is not already pending) that is - * responsible for actually dispatch events to JS. This implementation depends on the properties - * that 1) FrameCallbacks run after UI events have been processed in Choreographer.java 2) when we - * enqueue a runnable on the JS queue thread, it won't be called until after any previously enqueued - * JS jobs have finished processing - * - *

If JS is taking a long time processing events, then the UI events generated on the UI thread - * can be coalesced into fewer events so that when the runnable runs, we don't overload JS with a - * ton of events and make it get even farther behind. - * - *

Ideally, we don't need this and JS is fast enough to process all the events each frame, but - * bad things happen, including load on CPUs from the system, and we should handle this case well. - * - *

== Event Cookies == - * - *

An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only - * Events that have the same cookie can be coalesced. - * - *

Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK = - * 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000 - */ -public class EventDispatcher implements LifecycleEventListener { - - private static final Comparator EVENT_COMPARATOR = - new Comparator() { - @Override - public int compare(Event lhs, Event rhs) { - if (lhs == null && rhs == null) { - return 0; - } - if (lhs == null) { - return -1; - } - if (rhs == null) { - return 1; - } - - long diff = lhs.getTimestampMs() - rhs.getTimestampMs(); - if (diff == 0) { - return 0; - } else if (diff < 0) { - return -1; - } else { - return 1; - } - } - }; - - private final Object mEventsStagingLock = new Object(); - private final Object mEventsToDispatchLock = new Object(); - private final ReactApplicationContext mReactContext; - private final LongSparseArray mEventCookieToLastEventIdx = new LongSparseArray<>(); - private final Map mEventNameToEventId = MapBuilder.newHashMap(); - private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable(); - private final ArrayList mEventStaging = new ArrayList<>(); - private final ArrayList mListeners = new ArrayList<>(); - private final List mPostEventDispatchListeners = new ArrayList<>(); - private final ScheduleDispatchFrameCallback mCurrentFrameCallback = - new ScheduleDispatchFrameCallback(); - private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger(); - - private Event[] mEventsToDispatch = new Event[16]; - private int mEventsToDispatchSize = 0; - private volatile ReactEventEmitter mReactEventEmitter; - private short mNextEventTypeId = 0; - private volatile boolean mHasDispatchScheduled = false; - - public EventDispatcher(ReactApplicationContext reactContext) { - mReactContext = reactContext; - mReactContext.addLifecycleEventListener(this); - mReactEventEmitter = new ReactEventEmitter(mReactContext); - } +public interface EventDispatcher { /** Sends the given Event to JS, coalescing eligible events if JS is backed up. */ - public void dispatchEvent(Event event) { - Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized"); - - for (EventDispatcherListener listener : mListeners) { - listener.onEventDispatch(event); - } - - synchronized (mEventsStagingLock) { - mEventStaging.add(event); - Systrace.startAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - } - maybePostFrameCallbackFromNonUI(); - } - - public void dispatchAllEvents() { - maybePostFrameCallbackFromNonUI(); - } + void dispatchEvent(Event event); - private void maybePostFrameCallbackFromNonUI() { - if (mReactEventEmitter != null) { - // If the host activity is paused, the frame callback may not be currently - // posted. Ensure that it is so that this event gets delivered promptly. - mCurrentFrameCallback.maybePostFromNonUI(); - } else { - // No JS application has started yet, or resumed. This can happen when a ReactRootView is - // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any - // touch event dispatch will hit this codepath, and we simply queue them so that they - // are dispatched once ReactContext creation completes and JS app is running. - } - } + void dispatchAllEvents(); /** Add a listener to this EventDispatcher. */ - public void addListener(EventDispatcherListener listener) { - mListeners.add(listener); - } + void addListener(EventDispatcherListener listener); /** Remove a listener from this EventDispatcher. */ - public void removeListener(EventDispatcherListener listener) { - mListeners.remove(listener); - } - - public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.add(listener); - } - - public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.remove(listener); - } - - @Override - public void onHostResume() { - maybePostFrameCallbackFromNonUI(); - } - - @Override - public void onHostPause() { - stopFrameCallback(); - } - - @Override - public void onHostDestroy() { - stopFrameCallback(); - } - - public void onCatalystInstanceDestroyed() { - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - stopFrameCallback(); - } - }); - } - - private void stopFrameCallback() { - UiThreadUtil.assertOnUiThread(); - mCurrentFrameCallback.stop(); - } - - /** - * We use a staging data structure so that all UI events generated in a single frame are - * dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the - * UI thread is in the process of adding UI events and we might incorrectly send one event this - * frame and another from this frame during the next. - */ - private void moveStagedEventsToDispatchQueue() { - synchronized (mEventsStagingLock) { - synchronized (mEventsToDispatchLock) { - for (int i = 0; i < mEventStaging.size(); i++) { - Event event = mEventStaging.get(i); - - if (!event.canCoalesce()) { - addEventToEventsToDispatch(event); - continue; - } - - long eventCookie = - getEventCookie(event.getViewTag(), event.getEventName(), event.getCoalescingKey()); - - Event eventToAdd = null; - Event eventToDispose = null; - Integer lastEventIdx = mEventCookieToLastEventIdx.get(eventCookie); - - if (lastEventIdx == null) { - eventToAdd = event; - mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize); - } else { - Event lastEvent = mEventsToDispatch[lastEventIdx]; - Event coalescedEvent = event.coalesce(lastEvent); - if (coalescedEvent != lastEvent) { - eventToAdd = coalescedEvent; - mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize); - eventToDispose = lastEvent; - mEventsToDispatch[lastEventIdx] = null; - } else { - eventToDispose = event; - } - } - - if (eventToAdd != null) { - addEventToEventsToDispatch(eventToAdd); - } - if (eventToDispose != null) { - eventToDispose.dispose(); - } - } - } - mEventStaging.clear(); - } - } - - private long getEventCookie(int viewTag, String eventName, short coalescingKey) { - short eventTypeId; - Short eventIdObj = mEventNameToEventId.get(eventName); - if (eventIdObj != null) { - eventTypeId = eventIdObj; - } else { - eventTypeId = mNextEventTypeId++; - mEventNameToEventId.put(eventName, eventTypeId); - } - return getEventCookie(viewTag, eventTypeId, coalescingKey); - } - - private static long getEventCookie(int viewTag, short eventTypeId, short coalescingKey) { - return viewTag - | (((long) eventTypeId) & 0xffff) << 32 - | (((long) coalescingKey) & 0xffff) << 48; - } - - public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { - mReactEventEmitter.register(uiManagerType, eventEmitter); - } - - public void unregisterEventEmitter(@UIManagerType int uiManagerType) { - mReactEventEmitter.unregister(uiManagerType); - } - - private class ScheduleDispatchFrameCallback extends ChoreographerCompat.FrameCallback { - private volatile boolean mIsPosted = false; - private boolean mShouldStop = false; - - @Override - public void doFrame(long frameTimeNanos) { - UiThreadUtil.assertOnUiThread(); - - if (mShouldStop) { - mIsPosted = false; - } else { - post(); - } - - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ScheduleDispatchFrameCallback"); - try { - moveStagedEventsToDispatchQueue(); - - if (!mHasDispatchScheduled) { - mHasDispatchScheduled = true; - Systrace.startAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "ScheduleDispatchFrameCallback", - mHasDispatchScheduledCount.get()); - mReactContext.runOnJSQueueThread(mDispatchEventsRunnable); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - public void stop() { - mShouldStop = true; - } - - public void maybePost() { - if (!mIsPosted) { - mIsPosted = true; - post(); - } - } - - private void post() { - ReactChoreographer.getInstance() - .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback); - } - - public void maybePostFromNonUI() { - if (mIsPosted) { - return; - } + void removeListener(EventDispatcherListener listener); - // We should only hit this slow path when we receive events while the host activity is paused. - if (mReactContext.isOnUiQueueThread()) { - maybePost(); - } else { - mReactContext.runOnUiQueueThread( - new Runnable() { - @Override - public void run() { - maybePost(); - } - }); - } - } - } + void addBatchEventDispatchedListener(BatchEventDispatchedListener listener); - private class DispatchEventsRunnable implements Runnable { + void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener); - @Override - public void run() { - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable"); - try { - Systrace.endAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "ScheduleDispatchFrameCallback", - mHasDispatchScheduledCount.getAndIncrement()); - mHasDispatchScheduled = false; - Assertions.assertNotNull(mReactEventEmitter); - synchronized (mEventsToDispatchLock) { - if (mEventsToDispatchSize > 0) { - // We avoid allocating an array and iterator, and "sorting" if we don't need to. - // This occurs when the size of mEventsToDispatch is zero or one. - if (mEventsToDispatchSize > 1) { - Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR); - } - for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) { - Event event = mEventsToDispatch[eventIdx]; - // Event can be null if it has been coalesced into another event. - if (event == null) { - continue; - } - Systrace.endAsyncFlow( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); - event.dispatch(mReactEventEmitter); - event.dispose(); - } - clearEventsToDispatch(); - mEventCookieToLastEventIdx.clear(); - } - } - for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { - listener.onBatchEventDispatched(); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - } + void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter); - private void addEventToEventsToDispatch(Event event) { - if (mEventsToDispatchSize == mEventsToDispatch.length) { - mEventsToDispatch = Arrays.copyOf(mEventsToDispatch, 2 * mEventsToDispatch.length); - } - mEventsToDispatch[mEventsToDispatchSize++] = event; - } + void unregisterEventEmitter(@UIManagerType int uiManagerType); - private void clearEventsToDispatch() { - Arrays.fill(mEventsToDispatch, 0, mEventsToDispatchSize, null); - mEventsToDispatchSize = 0; - } + void onCatalystInstanceDestroyed(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java new file mode 100644 index 00000000000000..542f03616f1d33 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager.events; + +import android.util.LongSparseArray; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.modules.core.ChoreographerCompat; +import com.facebook.react.modules.core.ReactChoreographer; +import com.facebook.react.uimanager.common.UIManagerType; +import com.facebook.systrace.Systrace; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an + * intermediary between UI code generating events and JS, making sure we don't send more events than + * JS can process. + * + *

To use it, create a subclass of {@link Event} and call {@link #dispatchEvent(Event)} whenever + * there's a UI event to dispatch. + * + *

This class works by installing a Choreographer frame callback on the main thread. This + * callback then enqueues a runnable on the JS thread (if one is not already pending) that is + * responsible for actually dispatch events to JS. This implementation depends on the properties + * that 1) FrameCallbacks run after UI events have been processed in Choreographer.java 2) when we + * enqueue a runnable on the JS queue thread, it won't be called until after any previously enqueued + * JS jobs have finished processing + * + *

If JS is taking a long time processing events, then the UI events generated on the UI thread + * can be coalesced into fewer events so that when the runnable runs, we don't overload JS with a + * ton of events and make it get even farther behind. + * + *

Ideally, we don't need this and JS is fast enough to process all the events each frame, but + * bad things happen, including load on CPUs from the system, and we should handle this case well. + * + *

== Event Cookies == + * + *

An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only + * Events that have the same cookie can be coalesced. + * + *

Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK = + * 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000 + */ +public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListener { + + private static final Comparator EVENT_COMPARATOR = + new Comparator() { + @Override + public int compare(Event lhs, Event rhs) { + if (lhs == null && rhs == null) { + return 0; + } + if (lhs == null) { + return -1; + } + if (rhs == null) { + return 1; + } + + long diff = lhs.getTimestampMs() - rhs.getTimestampMs(); + if (diff == 0) { + return 0; + } else if (diff < 0) { + return -1; + } else { + return 1; + } + } + }; + + private final Object mEventsStagingLock = new Object(); + private final Object mEventsToDispatchLock = new Object(); + private final ReactApplicationContext mReactContext; + private final LongSparseArray mEventCookieToLastEventIdx = new LongSparseArray<>(); + private final Map mEventNameToEventId = MapBuilder.newHashMap(); + private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable(); + private final ArrayList mEventStaging = new ArrayList<>(); + private final ArrayList mListeners = new ArrayList<>(); + private final List mPostEventDispatchListeners = new ArrayList<>(); + private final ScheduleDispatchFrameCallback mCurrentFrameCallback = + new ScheduleDispatchFrameCallback(); + private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger(); + + private Event[] mEventsToDispatch = new Event[16]; + private int mEventsToDispatchSize = 0; + private volatile ReactEventEmitter mReactEventEmitter; + private short mNextEventTypeId = 0; + private volatile boolean mHasDispatchScheduled = false; + + public EventDispatcherImpl(ReactApplicationContext reactContext) { + mReactContext = reactContext; + mReactContext.addLifecycleEventListener(this); + mReactEventEmitter = new ReactEventEmitter(mReactContext); + } + + /** Sends the given Event to JS, coalescing eligible events if JS is backed up. */ + public void dispatchEvent(Event event) { + Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized"); + + for (EventDispatcherListener listener : mListeners) { + listener.onEventDispatch(event); + } + + synchronized (mEventsStagingLock) { + mEventStaging.add(event); + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); + } + maybePostFrameCallbackFromNonUI(); + } + + public void dispatchAllEvents() { + maybePostFrameCallbackFromNonUI(); + } + + private void maybePostFrameCallbackFromNonUI() { + if (mReactEventEmitter != null) { + // If the host activity is paused, the frame callback may not be currently + // posted. Ensure that it is so that this event gets delivered promptly. + mCurrentFrameCallback.maybePostFromNonUI(); + } else { + // No JS application has started yet, or resumed. This can happen when a ReactRootView is + // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any + // touch event dispatch will hit this codepath, and we simply queue them so that they + // are dispatched once ReactContext creation completes and JS app is running. + } + } + + /** Add a listener to this EventDispatcher. */ + public void addListener(EventDispatcherListener listener) { + mListeners.add(listener); + } + + /** Remove a listener from this EventDispatcher. */ + public void removeListener(EventDispatcherListener listener) { + mListeners.remove(listener); + } + + public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) { + mPostEventDispatchListeners.add(listener); + } + + public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) { + mPostEventDispatchListeners.remove(listener); + } + + @Override + public void onHostResume() { + maybePostFrameCallbackFromNonUI(); + } + + @Override + public void onHostPause() { + stopFrameCallback(); + } + + @Override + public void onHostDestroy() { + stopFrameCallback(); + } + + public void onCatalystInstanceDestroyed() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + stopFrameCallback(); + } + }); + } + + private void stopFrameCallback() { + UiThreadUtil.assertOnUiThread(); + mCurrentFrameCallback.stop(); + } + + /** + * We use a staging data structure so that all UI events generated in a single frame are + * dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the + * UI thread is in the process of adding UI events and we might incorrectly send one event this + * frame and another from this frame during the next. + */ + private void moveStagedEventsToDispatchQueue() { + synchronized (mEventsStagingLock) { + synchronized (mEventsToDispatchLock) { + for (int i = 0; i < mEventStaging.size(); i++) { + Event event = mEventStaging.get(i); + + if (!event.canCoalesce()) { + addEventToEventsToDispatch(event); + continue; + } + + long eventCookie = + getEventCookie(event.getViewTag(), event.getEventName(), event.getCoalescingKey()); + + Event eventToAdd = null; + Event eventToDispose = null; + Integer lastEventIdx = mEventCookieToLastEventIdx.get(eventCookie); + + if (lastEventIdx == null) { + eventToAdd = event; + mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize); + } else { + Event lastEvent = mEventsToDispatch[lastEventIdx]; + Event coalescedEvent = event.coalesce(lastEvent); + if (coalescedEvent != lastEvent) { + eventToAdd = coalescedEvent; + mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize); + eventToDispose = lastEvent; + mEventsToDispatch[lastEventIdx] = null; + } else { + eventToDispose = event; + } + } + + if (eventToAdd != null) { + addEventToEventsToDispatch(eventToAdd); + } + if (eventToDispose != null) { + eventToDispose.dispose(); + } + } + } + mEventStaging.clear(); + } + } + + private long getEventCookie(int viewTag, String eventName, short coalescingKey) { + short eventTypeId; + Short eventIdObj = mEventNameToEventId.get(eventName); + if (eventIdObj != null) { + eventTypeId = eventIdObj; + } else { + eventTypeId = mNextEventTypeId++; + mEventNameToEventId.put(eventName, eventTypeId); + } + return getEventCookie(viewTag, eventTypeId, coalescingKey); + } + + private static long getEventCookie(int viewTag, short eventTypeId, short coalescingKey) { + return viewTag + | (((long) eventTypeId) & 0xffff) << 32 + | (((long) coalescingKey) & 0xffff) << 48; + } + + public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { + mReactEventEmitter.register(uiManagerType, eventEmitter); + } + + public void unregisterEventEmitter(@UIManagerType int uiManagerType) { + mReactEventEmitter.unregister(uiManagerType); + } + + private class ScheduleDispatchFrameCallback extends ChoreographerCompat.FrameCallback { + private volatile boolean mIsPosted = false; + private boolean mShouldStop = false; + + @Override + public void doFrame(long frameTimeNanos) { + UiThreadUtil.assertOnUiThread(); + + if (mShouldStop) { + mIsPosted = false; + } else { + post(); + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ScheduleDispatchFrameCallback"); + try { + moveStagedEventsToDispatchQueue(); + + if (!mHasDispatchScheduled) { + mHasDispatchScheduled = true; + Systrace.startAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "ScheduleDispatchFrameCallback", + mHasDispatchScheduledCount.get()); + mReactContext.runOnJSQueueThread(mDispatchEventsRunnable); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + + public void stop() { + mShouldStop = true; + } + + public void maybePost() { + if (!mIsPosted) { + mIsPosted = true; + post(); + } + } + + private void post() { + ReactChoreographer.getInstance() + .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback); + } + + public void maybePostFromNonUI() { + if (mIsPosted) { + return; + } + + // We should only hit this slow path when we receive events while the host activity is paused. + if (mReactContext.isOnUiQueueThread()) { + maybePost(); + } else { + mReactContext.runOnUiQueueThread( + new Runnable() { + @Override + public void run() { + maybePost(); + } + }); + } + } + } + + private class DispatchEventsRunnable implements Runnable { + + @Override + public void run() { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable"); + try { + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "ScheduleDispatchFrameCallback", + mHasDispatchScheduledCount.getAndIncrement()); + mHasDispatchScheduled = false; + Assertions.assertNotNull(mReactEventEmitter); + synchronized (mEventsToDispatchLock) { + if (mEventsToDispatchSize > 0) { + // We avoid allocating an array and iterator, and "sorting" if we don't need to. + // This occurs when the size of mEventsToDispatch is zero or one. + if (mEventsToDispatchSize > 1) { + Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR); + } + for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) { + Event event = mEventsToDispatch[eventIdx]; + // Event can be null if it has been coalesced into another event. + if (event == null) { + continue; + } + Systrace.endAsyncFlow( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID()); + event.dispatch(mReactEventEmitter); + event.dispose(); + } + clearEventsToDispatch(); + mEventCookieToLastEventIdx.clear(); + } + } + for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { + listener.onBatchEventDispatched(); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + } + + private void addEventToEventsToDispatch(Event event) { + if (mEventsToDispatchSize == mEventsToDispatch.length) { + mEventsToDispatch = Arrays.copyOf(mEventsToDispatch, 2 * mEventsToDispatch.length); + } + mEventsToDispatch[mEventsToDispatchSize++] = event; + } + + private void clearEventsToDispatch() { + Arrays.fill(mEventsToDispatch, 0, mEventsToDispatchSize, null); + mEventsToDispatchSize = 0; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherProvider.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherProvider.java new file mode 100644 index 00000000000000..6f6537c068cf50 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager.events; + +/** + * An interface that can be implemented by a {@link com.facebook.react.bridge.ReactContext} to + * provide a first-class API for accessing the {@link EventDispatcher} from the {@link + * com.facebook.react.bridge.UIManager}. + */ +public interface EventDispatcherProvider { + + /** + * This method should always return an EventDispatcher, even if the instance doesn't exist; in + * that case it should return the empty {@link BlackHoleEventDispatcher}. + * + * @return An {@link EventDispatcher} to emit events to JS. + */ + EventDispatcher getEventDispatcher(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/util/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/util/BUCK index 1f67fb5365cf5e..6d31563ef3a53f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/util/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/util/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "util", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/util/BUCK b/ReactAndroid/src/main/java/com/facebook/react/util/BUCK index 84188101bbfc06..679245155e108d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/util/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/util/BUCK @@ -3,7 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_tar rn_android_library( name = "util", srcs = glob(["**/*.java"]), - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidDrawerLayoutManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidDrawerLayoutManagerDelegate.java index 5985c4ff1b946d..aee5f3f9c94820 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidDrawerLayoutManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidDrawerLayoutManagerDelegate.java @@ -47,13 +47,14 @@ public void setProperty(T view, String propName, @Nullable Object value) { } } - public void receiveCommand(AndroidDrawerLayoutManagerInterface viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case "openDrawer": - viewManager.openDrawer(view); + mViewManager.openDrawer(view); break; case "closeDrawer": - viewManager.closeDrawer(view); + mViewManager.closeDrawer(view); break; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwipeRefreshLayoutManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwipeRefreshLayoutManagerDelegate.java index 56a18f9aba0661..e5fc3ac7a2c2c2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwipeRefreshLayoutManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwipeRefreshLayoutManagerDelegate.java @@ -47,10 +47,11 @@ public void setProperty(T view, String propName, @Nullable Object value) { } } - public void receiveCommand(AndroidSwipeRefreshLayoutManagerInterface viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case "setNativeRefreshing": - viewManager.setNativeRefreshing(view, args.getBoolean(0)); + mViewManager.setNativeRefreshing(view, args.getBoolean(0)); break; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwitchManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwitchManagerDelegate.java index 5152015a9565b0..1b2f225d390ae7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwitchManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidSwitchManagerDelegate.java @@ -56,10 +56,11 @@ public void setProperty(T view, String propName, @Nullable Object value) { } } - public void receiveCommand(AndroidSwitchManagerInterface viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case "setNativeValue": - viewManager.setNativeValue(view, args.getBoolean(0)); + mViewManager.setNativeValue(view, args.getBoolean(0)); break; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidViewPagerManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidViewPagerManagerDelegate.java index 2f7b5d0853d12e..68c110b32412e8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidViewPagerManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/AndroidViewPagerManagerDelegate.java @@ -43,13 +43,14 @@ public void setProperty(T view, String propName, @Nullable Object value) { } } - public void receiveCommand(AndroidViewPagerManagerInterface viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case "setPage": - viewManager.setPage(view, args.getInt(0)); + mViewManager.setPage(view, args.getInt(0)); break; case "setPageWithoutAnimation": - viewManager.setPageWithoutAnimation(view, args.getInt(0)); + mViewManager.setPageWithoutAnimation(view, args.getInt(0)); break; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/BUCK b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/BUCK index d950d48939265a..039099fb0d634a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "viewmanagers", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/SwitchManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/SwitchManagerDelegate.java index 5e419f8bd3fe91..65d557ced192eb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/SwitchManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/viewmanagers/SwitchManagerDelegate.java @@ -15,7 +15,6 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.BaseViewManagerDelegate; import com.facebook.react.uimanager.BaseViewManagerInterface; -import com.facebook.react.uimanager.LayoutShadowNode; public class SwitchManagerDelegate & SwitchManagerInterface> extends BaseViewManagerDelegate { public SwitchManagerDelegate(U viewManager) { @@ -53,10 +52,11 @@ public void setProperty(T view, String propName, @Nullable Object value) { } } - public void receiveCommand(SwitchManagerInterface viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case "setValue": - viewManager.setValue(view, args.getBoolean(0)); + mViewManager.setValue(view, args.getBoolean(0)); break; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/common/BUCK index c9f4f1bda7ce78..2b6f2c1e932a8e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/common/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "common", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/android/androidx:annotation"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK index 9b24b8e6611515..ac71cc153ec774 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "drawer", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK index e60547c2a4101b..663357ac133d7f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK @@ -8,7 +8,7 @@ rn_android_library( name = "imageevents", srcs = IMAGE_EVENT_FILES, is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), @@ -32,7 +32,7 @@ rn_android_library( exclude = IMAGE_EVENT_FILES, ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK index 990f459c89c89a..4e9cafe129a021 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK @@ -7,7 +7,7 @@ rn_android_library( exclude = ["MultiSourceHelper.java"], ), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], @@ -22,7 +22,7 @@ rn_android_library( name = "withmultisource", srcs = ["MultiSourceHelper.java"], is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK index 6c69eb6af88884..53e6a5a987f584 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "modal", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK index ca04eca2315179..00b41242092acb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "picker", srcs = glob(["**/*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:appcompat"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK index 6ef5436e19a58a..af7909efc1e8ee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "progressbar", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK index ed41f3c10ebe43..736a73dc537560 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "scroll", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 1b2c5312381c47..c6db70340f8ff9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -91,6 +91,9 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private int mFinalAnimatedPositionScrollX = 0; private int mFinalAnimatedPositionScrollY = 0; + private int mLastStateUpdateScrollX = -1; + private int mLastStateUpdateScrollY = -1; + private final Rect mTempRect = new Rect(); public ReactHorizontalScrollView(Context context) { @@ -409,7 +412,7 @@ public boolean onTouchEvent(MotionEvent ev) { mVelocityHelper.calculateVelocity(ev); int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_UP && mDragging) { - updateStateOnScroll(getScrollX(), getScrollY()); + updateStateOnScroll(); float velocityX = mVelocityHelper.getXVelocity(); float velocityY = mVelocityHelper.getYVelocity(); @@ -604,11 +607,6 @@ public void draw(Canvas canvas) { * runnable that checks if we scrolled in the last frame and if so assumes we are still scrolling. */ private void handlePostTouchScrolling(int velocityX, int velocityY) { - // If we aren't going to do anything (send events or snap to page), we can early exit out. - if (!mSendMomentumEvents && !mPagingEnabled && !isScrollPerfLoggingEnabled()) { - return; - } - // Check if we are already handling this which may occur if this is called by both the touch up // and a fling call if (mPostTouchRunnable != null) { @@ -624,16 +622,29 @@ private void handlePostTouchScrolling(int velocityX, int velocityY) { new Runnable() { private boolean mSnappingToPage = false; + private boolean mRunning = true; + private int mStableFrames = 0; @Override public void run() { if (mActivelyScrolling) { - // We are still scrolling so we just post to check again a frame later + // We are still scrolling. mActivelyScrolling = false; - ViewCompat.postOnAnimationDelayed( - ReactHorizontalScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); + mStableFrames = 0; + mRunning = true; } else { - updateStateOnScroll(getScrollX(), getScrollY()); + // There has not been a scroll update since the last time this Runnable executed. + updateStateOnScroll(); + + // We keep checking for updates until the ScrollView has "stabilized" and hasn't + // scrolled for N consecutive frames. This number is arbitrary: big enough to catch + // a number of race conditions, but small enough to not cause perf regressions, etc. + // In anecdotal testing, it seemed like a decent number. + // Without this check, sometimes this Runnable stops executing too soon - it will + // fire before the first scroll event of an animated scroll/fling, and stop + // immediately. + mStableFrames++; + mRunning = (mStableFrames < 3); if (mPagingEnabled && !mSnappingToPage) { // Only if we have pagingEnabled and we have not snapped to the page do we @@ -647,14 +658,21 @@ public void run() { if (mSendMomentumEvents) { ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this); } - ReactHorizontalScrollView.this.mPostTouchRunnable = null; disableFpsListener(); } } + + // We are still scrolling so we just post to check again a frame later + if (mRunning) { + ViewCompat.postOnAnimationDelayed( + ReactHorizontalScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); + } else { + mPostTouchRunnable = null; + } } }; ViewCompat.postOnAnimationDelayed( - ReactHorizontalScrollView.this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); + this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); } /** Get current X position or position after current animation finishes, if any. */ @@ -960,7 +978,7 @@ public void reactSmoothScrollTo(int x, int y) { public void onAnimationUpdate(ValueAnimator valueAnimator) { int scrollValueX = (Integer) valueAnimator.getAnimatedValue("scrollX"); int scrollValueY = (Integer) valueAnimator.getAnimatedValue("scrollY"); - ReactHorizontalScrollView.this.scrollTo(scrollValueX, scrollValueY); + scrollTo(scrollValueX, scrollValueY); } }); mScrollAnimator.addListener( @@ -973,6 +991,7 @@ public void onAnimationEnd(Animator animator) { mFinalAnimatedPositionScrollX = -1; mFinalAnimatedPositionScrollY = -1; mScrollAnimator = null; + updateStateOnScroll(); } @Override @@ -1031,10 +1050,22 @@ private void updateStateOnScroll(int scrollX, int scrollY) { return; } + // Dedupe events to reduce JNI traffic + if (scrollX == mLastStateUpdateScrollX && scrollY == mLastStateUpdateScrollY) { + return; + } + + mLastStateUpdateScrollX = scrollX; + mLastStateUpdateScrollY = scrollY; + WritableMap map = new WritableNativeMap(); map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(scrollX)); map.putDouble(CONTENT_OFFSET_TOP, PixelUtil.toDIPFromPixel(scrollY)); mStateWrapper.updateState(map); } + + private void updateStateOnScroll() { + updateStateOnScroll(getScrollX(), getScrollY()); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 7a77b954415cda..5016bafe25e4ee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -96,6 +96,9 @@ public class ReactScrollView extends ScrollView private int mFinalAnimatedPositionScrollX; private int mFinalAnimatedPositionScrollY; + private int mLastStateUpdateScrollX = -1; + private int mLastStateUpdateScrollY = -1; + public ReactScrollView(ReactContext context) { this(context, null); } @@ -318,7 +321,7 @@ public boolean onTouchEvent(MotionEvent ev) { mVelocityHelper.calculateVelocity(ev); int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_UP && mDragging) { - updateStateOnScroll(getScrollX(), getScrollY()); + updateStateOnScroll(); float velocityX = mVelocityHelper.getXVelocity(); float velocityY = mVelocityHelper.getYVelocity(); @@ -491,11 +494,6 @@ public void draw(Canvas canvas) { * runnable that checks if we scrolled in the last frame and if so assumes we are still scrolling. */ private void handlePostTouchScrolling(int velocityX, int velocityY) { - // If we aren't going to do anything (send events or snap to page), we can early exit out. - if (!mSendMomentumEvents && !mPagingEnabled && !isScrollPerfLoggingEnabled()) { - return; - } - // Check if we are already handling this which may occur if this is called by both the touch up // and a fling call if (mPostTouchRunnable != null) { @@ -512,16 +510,29 @@ private void handlePostTouchScrolling(int velocityX, int velocityY) { new Runnable() { private boolean mSnappingToPage = false; + private boolean mRunning = true; + private int mStableFrames = 0; @Override public void run() { if (mActivelyScrolling) { - // We are still scrolling so we just post to check again a frame later + // We are still scrolling. mActivelyScrolling = false; - ViewCompat.postOnAnimationDelayed( - ReactScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); + mStableFrames = 0; + mRunning = true; } else { - updateStateOnScroll(getScrollX(), getScrollY()); + // There has not been a scroll update since the last time this Runnable executed. + updateStateOnScroll(); + + // We keep checking for updates until the ScrollView has "stabilized" and hasn't + // scrolled for N consecutive frames. This number is arbitrary: big enough to catch + // a number of race conditions, but small enough to not cause perf regressions, etc. + // In anecdotal testing, it seemed like a decent number. + // Without this check, sometimes this Runnable stops executing too soon - it will + // fire before the first scroll event of an animated scroll/fling, and stop + // immediately. + mStableFrames++; + mRunning = (mStableFrames < 3); if (mPagingEnabled && !mSnappingToPage) { // Only if we have pagingEnabled and we have not snapped to the page do we @@ -535,14 +546,21 @@ public void run() { if (mSendMomentumEvents) { ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this); } - ReactScrollView.this.mPostTouchRunnable = null; disableFpsListener(); } } + + // We are still scrolling so we just post to check again a frame later + if (mRunning) { + ViewCompat.postOnAnimationDelayed( + ReactScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); + } else { + mPostTouchRunnable = null; + } } }; ViewCompat.postOnAnimationDelayed( - ReactScrollView.this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); + this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); } /** Get current X position or position after current animation finishes, if any. */ @@ -831,7 +849,7 @@ public void reactSmoothScrollTo(int x, int y) { public void onAnimationUpdate(ValueAnimator valueAnimator) { int scrollValueX = (Integer) valueAnimator.getAnimatedValue("scrollX"); int scrollValueY = (Integer) valueAnimator.getAnimatedValue("scrollY"); - ReactScrollView.this.scrollTo(scrollValueX, scrollValueY); + scrollTo(scrollValueX, scrollValueY); } }); mScrollAnimator.addListener( @@ -844,6 +862,7 @@ public void onAnimationEnd(Animator animator) { mFinalAnimatedPositionScrollX = -1; mFinalAnimatedPositionScrollY = -1; mScrollAnimator = null; + updateStateOnScroll(); } @Override @@ -954,10 +973,22 @@ private void updateStateOnScroll(int scrollX, int scrollY) { return; } + // Dedupe events to reduce JNI traffic + if (scrollX == mLastStateUpdateScrollX && scrollY == mLastStateUpdateScrollY) { + return; + } + + mLastStateUpdateScrollX = scrollX; + mLastStateUpdateScrollY = scrollY; + WritableMap map = new WritableNativeMap(); map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(scrollX)); map.putDouble(CONTENT_OFFSET_TOP, PixelUtil.toDIPFromPixel(scrollY)); mStateWrapper.updateState(map); } + + private void updateStateOnScroll() { + updateStateOnScroll(getScrollX(), getScrollY()); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK index 4aa48da0029ddf..63f1658e470b6f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "slider", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java index 2bab0c053204a3..1cc148fe0d2605 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java @@ -259,7 +259,7 @@ public long measure( YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { SeekBar reactSlider = new ReactSlider(context, null, STYLE); final int spec = View.MeasureSpec.makeMeasureSpec( diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK index f4e77eb6322462..528c4af8a55056 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "swiperefresh", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java index 5d7a4343db66d4..c8462a138ccdc5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java @@ -131,7 +131,7 @@ public void setProgressViewOffset(final ReactSwipeRefreshLayout view, final floa @Override public void setNativeRefreshing(ReactSwipeRefreshLayout view, boolean value) { - // TODO(T52835863): Implement when view commands start using delegates generated by JS. + setRefreshing(view, value); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK index 677c0ddb179f57..cd59cc2b720430 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "switchview", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:appcompat"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java index 0a6b8c8762e740..c2196f3ff142dc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java @@ -177,7 +177,7 @@ public void setTrackTintColor(ReactSwitch view, @Nullable Integer color) { @Override public void setNativeValue(ReactSwitch view, boolean value) { - // TODO(T52835863): Implement when view commands start using delegates generated by JS. + setValueInternal(view, value); } @Override @@ -210,7 +210,7 @@ public long measure( YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { ReactSwitch view = new ReactSwitch(context); view.setShowText(false); int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK index 6b4ef8857c33a5..cd966af069962c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "text", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index c3e248bc33b309..7984406e0e7d8f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -120,7 +120,7 @@ public long measure( YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { return TextLayoutManager.measureText( context, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java index f98035c179bba1..8bde57f62d7c10 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java @@ -20,7 +20,9 @@ import android.text.TextPaint; import android.util.LayoutDirection; import android.util.LruCache; +import android.view.View; import androidx.annotation.Nullable; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.PixelUtil; @@ -35,6 +37,11 @@ /** Class responsible of creating {@link Spanned} object for the JS representation of Text */ public class TextLayoutManager { + // TODO T67606397: Refactor configuration of fabric logs + private static final boolean ENABLE_MEASURE_LOGGING = false; + + private static final String TAG = "TextLayoutManager"; + // It's important to pass the ANTI_ALIAS_FLAG flag to the constructor rather than setting it // later by calling setFlags. This is because the latter approach triggers a bug on Android 4.4.2. // The bug is that unicode emoticons aren't measured properly which causes text to be clipped. @@ -80,7 +87,7 @@ private static void buildSpannableFromFragment( sb.append(TextTransform.apply(fragment.getString("string"), textAttributes.mTextTransform)); int end = sb.length(); - int reactTag = fragment.getInt("reactTag"); + int reactTag = fragment.hasKey("reactTag") ? fragment.getInt("reactTag") : View.NO_ID; if (fragment.hasKey(ViewProps.IS_ATTACHMENT) && fragment.getBoolean(ViewProps.IS_ATTACHMENT)) { float width = PixelUtil.toPixelFromSP(fragment.getDouble(ViewProps.WIDTH)); @@ -216,7 +223,7 @@ public static long measureText( float height, YogaMeasureMode heightYogaMeasureMode, ReactTextViewManagerCallback reactTextViewManagerCallback, - @Nullable int[] attachmentsPositions) { + @Nullable float[] attachmentsPositions) { // TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic) TextPaint textPaint = sTextPaintInstance; @@ -234,6 +241,7 @@ public static long measureText( if (text == null) { throw new IllegalStateException("Spannable element has not been prepared in onBeforeLayout"); } + BoringLayout.Metrics boring = BoringLayout.isBoring(text, textPaint); float desiredWidth = boring == null ? Layout.getDesiredWidth(text, textPaint) : Float.NaN; @@ -412,16 +420,34 @@ public static long measureText( // The attachment array returns the positions of each of the attachments as attachmentsPositions[attachmentPosition] = - (int) Math.ceil(PixelUtil.toSPFromPixel(placeholderTopPosition)); + PixelUtil.toSPFromPixel(placeholderTopPosition); attachmentsPositions[attachmentPosition + 1] = - (int) Math.ceil(PixelUtil.toSPFromPixel(placeholderLeftPosition)); + PixelUtil.toSPFromPixel(placeholderLeftPosition); attachmentIndex++; } } } - return YogaMeasureOutput.make( - PixelUtil.toSPFromPixel(calculatedWidth), PixelUtil.toSPFromPixel(calculatedHeight)); + float widthInSP = PixelUtil.toSPFromPixel(calculatedWidth); + float heightInSP = PixelUtil.toSPFromPixel(calculatedHeight); + + if (ENABLE_MEASURE_LOGGING) { + FLog.e( + TAG, + "TextMeasure call ('" + + text + + "'): w: " + + calculatedWidth + + " px - h: " + + calculatedHeight + + " px - w : " + + widthInSP + + " sp - h: " + + heightInSP + + " sp"); + } + + return YogaMeasureOutput.make(widthInSP, heightInSP); } // TODO T31905686: This class should be private diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK index d882979b4893af..fef31ebea6c4aa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "frescosupport", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK index ba19532ffc7fa1..3b408b3d319e69 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "textinput", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], required_for_source_only_abi = True, visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/unimplementedview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/unimplementedview/BUCK index a4d7b073035547..24d0a3f2d916fa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/unimplementedview/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/unimplementedview/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "unimplementedview", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK index 1b9b8e05181547..66386a0ffbfd5c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK @@ -4,7 +4,7 @@ rn_android_library( name = "view", srcs = glob(["*.java"]), is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], provided_deps = [ react_native_dep("third-party/android/androidx:core"), react_native_dep("third-party/android/androidx:fragment"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java index add5f38812baa0..4fb23c95a960f2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java @@ -37,15 +37,10 @@ public static Drawable createDrawableFromJSDescription( String type = drawableDescriptionDict.getString("type"); if ("ThemeAttrAndroid".equals(type)) { String attr = drawableDescriptionDict.getString("attribute"); - SoftAssertions.assertNotNull(attr); - int attrID = context.getResources().getIdentifier(attr, "attr", "android"); - if (attrID == 0) { + int attrId = getAttrId(context, attr); + if (!context.getTheme().resolveAttribute(attrId, sResolveOutValue, true)) { throw new JSApplicationIllegalArgumentException( - "Attribute " + attr + " couldn't be found in the resource list"); - } - if (!context.getTheme().resolveAttribute(attrID, sResolveOutValue, true)) { - throw new JSApplicationIllegalArgumentException( - "Attribute " + attr + " couldn't be resolved into a drawable"); + "Attribute " + attr + " with id " + attrId + " couldn't be resolved into a drawable"); } Drawable drawable = getDefaultThemeDrawable(context); return setRadius(drawableDescriptionDict, drawable); @@ -57,6 +52,18 @@ public static Drawable createDrawableFromJSDescription( } } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static int getAttrId(Context context, String attr) { + SoftAssertions.assertNotNull(attr); + if ("selectableItemBackground".equals(attr)) { + return android.R.attr.selectableItemBackground; + } else if ("selectableItemBackgroundBorderless".equals(attr)) { + return android.R.attr.selectableItemBackgroundBorderless; + } else { + return context.getResources().getIdentifier(attr, "attr", "android"); + } + } + private static Drawable getDefaultThemeDrawable(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return context.getResources().getDrawable(sResolveOutValue.resourceId, context.getTheme()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index beb51d86fb4e39..851ec10c6f331c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -24,7 +24,7 @@ import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.Spacing; import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; @@ -233,14 +233,12 @@ public void setFocusable(final ReactViewGroup view, boolean focusable) { new View.OnClickListener() { @Override public void onClick(View v) { - UIManagerModule uiManager = - ((ReactContext) view.getContext()).getNativeModule(UIManagerModule.class); - - if (uiManager == null) { + final EventDispatcher mEventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag( + (ReactContext) view.getContext(), view.getId()); + if (mEventDispatcher == null) { return; } - - final EventDispatcher mEventDispatcher = uiManager.getEventDispatcher(); mEventDispatcher.dispatchEvent(new ViewGroupClickEvent(view.getId())); } }); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK deleted file mode 100644 index 77179a7e98f8f1..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK +++ /dev/null @@ -1,31 +0,0 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") - -rn_android_library( - name = "viewpager", - srcs = glob(["**/*.java"]), - is_androidx = True, - labels = ["supermodule:android/default/public.react_native.infra"], - provided_deps = [ - react_native_dep("third-party/android/androidx:annotation"), - react_native_dep("third-party/android/androidx:core"), - react_native_dep("third-party/android/androidx:fragment"), - react_native_dep("third-party/android/androidx:legacy-support-core-ui"), - react_native_dep("third-party/android/androidx:legacy-support-core-utils"), - ], - required_for_source_only_abi = True, - visibility = [ - "PUBLIC", - ], - deps = [ - react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), - react_native_dep("third-party/java/infer-annotations:infer-annotations"), - react_native_dep("third-party/java/jsr-305:jsr-305"), - react_native_target("java/com/facebook/react/bridge:bridge"), - react_native_target("java/com/facebook/react/common:common"), - react_native_target("java/com/facebook/react/module/annotations:annotations"), - react_native_target("java/com/facebook/react/uimanager:uimanager"), - react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), - react_native_target("java/com/facebook/react/views/scroll:scroll"), - react_native_target("java/com/facebook/react/viewmanagers:viewmanagers"), - ], -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java deleted file mode 100644 index 2fcc64de002273..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollEvent.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.viewpager; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted by {@link ReactViewPager} when user scrolls between pages (or when animating - * between pages). - * - *

Additional data provided by this event: - * - *

    - *
  • position - index of first page from the left that is currently visible - *
  • offset - value from range [0,1) describing stage between page transitions. Value x means - * that (1 - x) fraction of the page at "position" index is visible, and x fraction of the - * next page is visible. - *
- */ -/* package */ class PageScrollEvent extends Event { - - public static final String EVENT_NAME = "topPageScroll"; - - private final int mPosition; - private final float mOffset; - - protected PageScrollEvent(int viewTag, int position, float offset) { - super(viewTag); - mPosition = position; - - // folly::toJson default options don't support serialize NaN or Infinite value - mOffset = (Float.isInfinite(offset) || Float.isNaN(offset)) ? 0.0f : offset; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); - } - - private WritableMap serializeEventData() { - WritableMap eventData = Arguments.createMap(); - eventData.putInt("position", mPosition); - eventData.putDouble("offset", mOffset); - return eventData; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java deleted file mode 100644 index 68585a75ca5fd7..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.viewpager; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted by {@link ReactViewPager} when user scrolling state changed. - * - *

Additional data provided by this event: - * - *

    - *
  • pageScrollState - {Idle,Dragging,Settling} - *
- */ -class PageScrollStateChangedEvent extends Event { - - public static final String EVENT_NAME = "topPageScrollStateChanged"; - - private final String mPageScrollState; - - protected PageScrollStateChangedEvent(int viewTag, String pageScrollState) { - super(viewTag); - mPageScrollState = pageScrollState; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); - } - - private WritableMap serializeEventData() { - WritableMap eventData = Arguments.createMap(); - eventData.putString("pageScrollState", mPageScrollState); - return eventData; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java deleted file mode 100644 index 8d59e887cb6d10..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageSelectedEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.viewpager; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted by {@link ReactViewPager} when selected page changes. - * - *

Additional data provided by this event: - * - *

    - *
  • position - index of page that has been selected - *
- */ -/* package */ class PageSelectedEvent extends Event { - - public static final String EVENT_NAME = "topPageSelected"; - - private final int mPosition; - - protected PageSelectedEvent(int viewTag, int position) { - super(viewTag); - mPosition = position; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); - } - - private WritableMap serializeEventData() { - WritableMap eventData = Arguments.createMap(); - eventData.putInt("position", mPosition); - return eventData; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java deleted file mode 100644 index 398751a8f6e79e..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.viewpager; - -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import androidx.viewpager.widget.PagerAdapter; -import androidx.viewpager.widget.ViewPager; -import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.events.EventDispatcher; -import com.facebook.react.uimanager.events.NativeGestureUtil; -import java.util.ArrayList; -import java.util.List; - -/** - * Wrapper view for {@link ViewPager}. It's forwarding calls to {@link ViewGroup#addView} to add - * views to custom {@link PagerAdapter} instance which is used by {@link NativeViewHierarchyManager} - * to add children nodes according to react views hierarchy. - */ -public class ReactViewPager extends ViewPager { - - private class Adapter extends PagerAdapter { - - private final List mViews = new ArrayList<>(); - private boolean mIsViewPagerInIntentionallyInconsistentState = false; - - void addView(View child, int index) { - mViews.add(index, child); - notifyDataSetChanged(); - // This will prevent view pager from detaching views for pages that are not currently selected - // We need to do that since {@link ViewPager} relies on layout passes to position those views - // in a right way (also thanks to {@link ReactViewPagerManager#needsCustomLayoutForChildren} - // returning {@code true}). Currently we only call {@link View#measure} and - // {@link View#layout} after yoga step. - - // TODO(7323049): Remove this workaround once we figure out a way to re-layout some views on - // request - setOffscreenPageLimit(mViews.size()); - } - - void removeViewAt(int index) { - mViews.remove(index); - notifyDataSetChanged(); - - // TODO(7323049): Remove this workaround once we figure out a way to re-layout some views on - // request - setOffscreenPageLimit(mViews.size()); - } - - /** Replace a set of views to the ViewPager adapter and update the ViewPager */ - void setViews(List views) { - mViews.clear(); - mViews.addAll(views); - notifyDataSetChanged(); - - // we want to make sure we return POSITION_NONE for every view here, since this is only - // called after a removeAllViewsFromAdapter - mIsViewPagerInIntentionallyInconsistentState = false; - } - - /** - * Remove all the views from the adapter and de-parents them from the ViewPager After calling - * this, it is expected that notifyDataSetChanged should be called soon afterwards. - */ - void removeAllViewsFromAdapter(ViewPager pager) { - mViews.clear(); - pager.removeAllViews(); - // set this, so that when the next addViews is called, we return POSITION_NONE for every - // entry so we can remove whichever views we need to and add the ones that we need to. - mIsViewPagerInIntentionallyInconsistentState = true; - } - - View getViewAt(int index) { - return mViews.get(index); - } - - @Override - public int getCount() { - return mViews.size(); - } - - @Override - public int getItemPosition(Object object) { - // if we've removed all views, we want to return POSITION_NONE intentionally - return mIsViewPagerInIntentionallyInconsistentState || !mViews.contains(object) - ? POSITION_NONE - : mViews.indexOf(object); - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - View view = mViews.get(position); - container.addView(view, 0, generateDefaultLayoutParams()); - return view; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - container.removeView((View) object); - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return view == object; - } - } - - private class PageChangeListener implements ViewPager.OnPageChangeListener { - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mEventDispatcher.dispatchEvent(new PageScrollEvent(getId(), position, positionOffset)); - } - - @Override - public void onPageSelected(int position) { - if (!mIsCurrentItemFromJs) { - mEventDispatcher.dispatchEvent(new PageSelectedEvent(getId(), position)); - } - } - - @Override - public void onPageScrollStateChanged(int state) { - String pageScrollState; - switch (state) { - case SCROLL_STATE_IDLE: - pageScrollState = "idle"; - break; - case SCROLL_STATE_DRAGGING: - pageScrollState = "dragging"; - break; - case SCROLL_STATE_SETTLING: - pageScrollState = "settling"; - break; - default: - throw new IllegalStateException("Unsupported pageScrollState"); - } - mEventDispatcher.dispatchEvent(new PageScrollStateChangedEvent(getId(), pageScrollState)); - } - } - - private final EventDispatcher mEventDispatcher; - private boolean mIsCurrentItemFromJs; - private boolean mScrollEnabled = true; - - public ReactViewPager(ReactContext reactContext) { - super(reactContext); - mEventDispatcher = - Assertions.assertNotNull(reactContext.getNativeModule(UIManagerModule.class)) - .getEventDispatcher(); - mIsCurrentItemFromJs = false; - setOnPageChangeListener(new PageChangeListener()); - setAdapter(new Adapter()); - } - - @Override - public Adapter getAdapter() { - return (Adapter) super.getAdapter(); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (!mScrollEnabled) { - return false; - } - - try { - if (super.onInterceptTouchEvent(ev)) { - NativeGestureUtil.notifyNativeGestureStarted(this, ev); - return true; - } - } catch (IllegalArgumentException e) { - // Log and ignore the error. This seems to be a bug in the android SDK and - // this is the commonly accepted workaround. - // https://tinyurl.com/mw6qkod (Stack Overflow) - FLog.w(ReactConstants.TAG, "Error intercepting touch event.", e); - } - - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (!mScrollEnabled) { - return false; - } - - try { - return super.onTouchEvent(ev); - } catch (IllegalArgumentException e) { - // Log and ignore the error. This seems to be a bug in the android SDK and - // this is the commonly accepted workaround. - // https://fburl.com/5d3iw7d9 - FLog.w(ReactConstants.TAG, "Error handling touch event.", e); - } - - return false; - } - - public void setCurrentItemFromJs(int item, boolean animated) { - mIsCurrentItemFromJs = true; - setCurrentItem(item, animated); - mIsCurrentItemFromJs = false; - } - - public void setScrollEnabled(boolean scrollEnabled) { - mScrollEnabled = scrollEnabled; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - // The viewpager reset an internal flag on this method so we need to run another layout pass - // after attaching to window. - this.requestLayout(); - post(measureAndLayout); - } - - private final Runnable measureAndLayout = - new Runnable() { - @Override - public void run() { - measure( - MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); - layout(getLeft(), getTop(), getRight(), getBottom()); - } - }; - - /*package*/ void addViewToAdapter(View child, int index) { - getAdapter().addView(child, index); - } - - /*package*/ void removeViewFromAdapter(int index) { - getAdapter().removeViewAt(index); - } - - /*package*/ int getViewCountInAdapter() { - return getAdapter().getCount(); - } - - /*package*/ View getViewFromAdapter(int index) { - return getAdapter().getViewAt(index); - } - - public void setViews(List views) { - getAdapter().setViews(views); - } - - public void removeAllViewsFromAdapter() { - getAdapter().removeAllViewsFromAdapter(this); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java deleted file mode 100644 index 010cdfa28728a9..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.viewpager; - -import android.view.View; -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.ViewGroupManager; -import com.facebook.react.uimanager.ViewManagerDelegate; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.viewmanagers.AndroidViewPagerManagerDelegate; -import com.facebook.react.viewmanagers.AndroidViewPagerManagerInterface; -import java.util.Map; - -/** Instance of {@link ViewManager} that provides native {@link ViewPager} view. */ -@ReactModule(name = ReactViewPagerManager.REACT_CLASS) -public class ReactViewPagerManager extends ViewGroupManager - implements AndroidViewPagerManagerInterface { - - public static final String REACT_CLASS = "AndroidViewPager"; - - public static final int COMMAND_SET_PAGE = 1; - public static final int COMMAND_SET_PAGE_WITHOUT_ANIMATION = 2; - - private final ViewManagerDelegate mDelegate; - - public ReactViewPagerManager() { - mDelegate = new AndroidViewPagerManagerDelegate<>(this); - } - - @Override - public String getName() { - return REACT_CLASS; - } - - @Override - protected ReactViewPager createViewInstance(ThemedReactContext reactContext) { - return new ReactViewPager(reactContext); - } - - @Override - @ReactProp(name = "scrollEnabled", defaultBoolean = true) - public void setScrollEnabled(ReactViewPager viewPager, boolean value) { - viewPager.setScrollEnabled(value); - } - - @Override - public boolean needsCustomLayoutForChildren() { - return true; - } - - @Override - public Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.of( - PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"), - PageScrollStateChangedEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onPageScrollStateChanged"), - PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected")); - } - - @Override - public Map getCommandsMap() { - return MapBuilder.of( - "setPage", COMMAND_SET_PAGE, "setPageWithoutAnimation", COMMAND_SET_PAGE_WITHOUT_ANIMATION); - } - - @Override - public void receiveCommand( - ReactViewPager viewPager, int commandType, @Nullable ReadableArray args) { - Assertions.assertNotNull(viewPager); - Assertions.assertNotNull(args); - switch (commandType) { - case COMMAND_SET_PAGE: - { - viewPager.setCurrentItemFromJs(args.getInt(0), true); - return; - } - case COMMAND_SET_PAGE_WITHOUT_ANIMATION: - { - viewPager.setCurrentItemFromJs(args.getInt(0), false); - return; - } - default: - throw new IllegalArgumentException( - String.format( - "Unsupported command %d received by %s.", commandType, getClass().getSimpleName())); - } - } - - @Override - public void receiveCommand( - ReactViewPager viewPager, String commandType, @Nullable ReadableArray args) { - Assertions.assertNotNull(viewPager); - Assertions.assertNotNull(args); - switch (commandType) { - case "setPage": - { - viewPager.setCurrentItemFromJs(args.getInt(0), true); - return; - } - case "setPageWithoutAnimation": - { - viewPager.setCurrentItemFromJs(args.getInt(0), false); - return; - } - default: - throw new IllegalArgumentException( - String.format( - "Unsupported command %d received by %s.", commandType, getClass().getSimpleName())); - } - } - - @Override - public void addView(ReactViewPager parent, View child, int index) { - parent.addViewToAdapter(child, index); - } - - @Override - public int getChildCount(ReactViewPager parent) { - return parent.getViewCountInAdapter(); - } - - @Override - public View getChildAt(ReactViewPager parent, int index) { - return parent.getViewFromAdapter(index); - } - - @Override - public void removeViewAt(ReactViewPager parent, int index) { - parent.removeViewFromAdapter(index); - } - - @Override - @ReactProp(name = "pageMargin", defaultInt = 0) - public void setPageMargin(ReactViewPager pager, int margin) { - pager.setPageMargin((int) PixelUtil.toPixelFromDIP(margin)); - } - - @Override - @ReactProp(name = "peekEnabled", defaultBoolean = false) - public void setPeekEnabled(ReactViewPager pager, boolean peekEnabled) { - pager.setClipToPadding(!peekEnabled); - } - - @Override - public void setInitialPage(ReactViewPager view, int value) {} - - @Override - public void setKeyboardDismissMode(ReactViewPager view, @Nullable String value) {} - - @Override - public void setPage(ReactViewPager view, int page) { - // TODO(T52835863): Implement when view commands start using delegates generated by JS. - } - - @Override - public void setPageWithoutAnimation(ReactViewPager view, int page) { - // TODO(T52835863): Implement when view commands start using delegates generated by JS. - } - - @Override - public ViewManagerDelegate getDelegate() { - return mDelegate; - } -} diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp index 6e66c06ed1abe8..698ab5615de6f0 100644 --- a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp +++ b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNIVanilla.cpp @@ -388,6 +388,13 @@ static void jni_YGNodeCalculateLayoutJNI( if (throwable.get()) { env->Throw(throwable.get()); } + } catch (const std::logic_error& ex) { + env->ExceptionClear(); + jclass cl = env->FindClass("Ljava/lang/IllegalStateException;"); + static const jmethodID methodId = facebook::yoga::vanillajni::getMethodId( + env, cl, "", "(Ljava/lang/String;)V"); + auto throwable = env->NewObject(cl, methodId, env->NewStringUTF(ex.what())); + env->Throw(static_cast(throwable)); } } diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk index dabd4b7c5125c9..38a51019ee0f44 100644 --- a/ReactAndroid/src/main/jni/react/jni/Android.mk +++ b/ReactAndroid/src/main/jni/react/jni/Android.mk @@ -69,6 +69,7 @@ $(call import-module,cxxreact) $(call import-module,jsi) $(call import-module,jsiexecutor) $(call import-module,callinvoker) +$(call import-module,reactperflogger) $(call import-module,hermes) $(call import-module,runtimeexecutor) diff --git a/ReactAndroid/src/main/jni/react/jni/BUCK b/ReactAndroid/src/main/jni/react/jni/BUCK index 931a8933d92986..de79bff729e9cb 100644 --- a/ReactAndroid/src/main/jni/react/jni/BUCK +++ b/ReactAndroid/src/main/jni/react/jni/BUCK @@ -43,7 +43,7 @@ rn_xplat_cxx_library( "-Wno-inconsistent-missing-override", ], fbandroid_allow_jni_merging = True, - labels = ["supermodule:android/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = ANDROID, preprocessor_flags = [ "-DLOG_TAG=\"ReactNativeJNI\"", diff --git a/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp b/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp index 0ad1f03b37b95b..e8281b8f1cbed2 100644 --- a/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp +++ b/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp @@ -50,6 +50,26 @@ std::string JavaNativeModule::getName() { return getNameMethod(wrapper_)->toStdString(); } +std::string JavaNativeModule::getSyncMethodName(unsigned int reactMethodId) { + if (reactMethodId >= syncMethods_.size()) { + throw std::invalid_argument(folly::to( + "methodId ", + reactMethodId, + " out of range [0..", + syncMethods_.size(), + "]")); + } + + auto &methodInvoker = syncMethods_[reactMethodId]; + + if (!methodInvoker.hasValue()) { + throw std::invalid_argument(folly::to( + "methodId ", reactMethodId, " is not a recognized sync method")); + } + + return methodInvoker->getMethodName(); +} + std::vector JavaNativeModule::getMethods() { std::vector ret; syncMethods_.clear(); @@ -69,6 +89,7 @@ std::vector JavaNativeModule::getMethods() { syncMethods_.begin() + methodIndex, MethodInvoker( desc->getMethod(), + methodName, desc->getSignature(), getName() + "." + methodName, true)); @@ -148,6 +169,7 @@ NewJavaNativeModule::NewJavaNativeModule( auto name = desc->getName(); methods_.emplace_back( desc->getMethod(), + desc->getName(), desc->getSignature(), moduleName + "." + name, type == "syncHook"); diff --git a/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.h b/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.h index 5c2d7cb9677fca..f4b46ce66b4ff7 100644 --- a/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.h +++ b/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.h @@ -69,6 +69,7 @@ class JavaNativeModule : public NativeModule { messageQueueThread_(std::move(messageQueueThread)) {} std::string getName() override; + std::string getSyncMethodName(unsigned int reactMethodId) override; folly::dynamic getConstants() override; std::vector getMethods() override; void invoke(unsigned int reactMethodId, folly::dynamic &¶ms, int callId) diff --git a/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp b/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp index 571495b77232bb..496a99cdca3abd 100644 --- a/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp +++ b/ReactAndroid/src/main/jni/react/jni/MethodInvoker.cpp @@ -190,10 +190,12 @@ std::size_t countJsArgs(const std::string &signature) { MethodInvoker::MethodInvoker( alias_ref method, + std::string methodName, std::string signature, std::string traceName, bool isSync) : method_(method->getMethodID()), + methodName_(methodName), signature_(signature), jsArgCount_(countJsArgs(signature) - 2), traceName_(std::move(traceName)), @@ -203,6 +205,10 @@ MethodInvoker::MethodInvoker( << "Non-sync hooks cannot have a non-void return type"; } +std::string MethodInvoker::getMethodName() const { + return methodName_; +} + MethodCallResult MethodInvoker::invoke( std::weak_ptr &instance, alias_ref module, diff --git a/ReactAndroid/src/main/jni/react/jni/MethodInvoker.h b/ReactAndroid/src/main/jni/react/jni/MethodInvoker.h index ee3eb087dddb9f..67f5fc06d637e9 100644 --- a/ReactAndroid/src/main/jni/react/jni/MethodInvoker.h +++ b/ReactAndroid/src/main/jni/react/jni/MethodInvoker.h @@ -37,6 +37,7 @@ class MethodInvoker { public: MethodInvoker( jni::alias_ref method, + std::string methodName, std::string signature, std::string traceName, bool isSync); @@ -46,12 +47,15 @@ class MethodInvoker { jni::alias_ref module, const folly::dynamic ¶ms); + std::string getMethodName() const; + bool isSyncHook() const { return isSync_; } private: jmethodID method_; + std::string methodName_; std::string signature_; std::size_t jsArgCount_; std::string traceName_; diff --git a/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK b/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK index 1473f92d1099f4..2a86bc6af780b5 100644 --- a/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK +++ b/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK @@ -2,164 +2,174 @@ load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library", "rn_prebuilt_jar") rn_android_library( - name = "powermock", - visibility = ["//ReactAndroid/..."], + name = "powermock2", + visibility = ["PUBLIC"], exported_deps = [ - ":javassist", - ":mockito-all", - ":powermock-api-mockito", + ":javassist-prebuilt", + ":powermock-api-mockito2", ":powermock-api-support", ":powermock-classloading-base", - ":powermock-classloading-xstream", + ":powermock-classloading-xstream-prebuilt", ":powermock-core", - ":powermock-module-junit4-rule", + ":powermock-module-junit-common-prebuilt", + ":powermock-module-junit-rule-prebuilt", ":powermock-reflect", - ":xmlpull", - ":xpp3", - ":xstream", + ":xstream-prebuilt", + ], +) + +rn_android_library( + name = "powermock-reflect", + visibility = ["PUBLIC"], + exported_deps = [ + ":byte-buddy", + ":objenesis", + ":powermock-reflect-prebuilt", ], ) rn_prebuilt_jar( - name = "powermock-core", - binary_jar = ":download-powermock-core.jar", + name = "byte-buddy", + binary_jar = ":byte-buddy-binary.jar", visibility = ["//ReactAndroid/..."], ) fb_native.remote_file( - name = "download-powermock-core.jar", - sha1 = "ea04e79244e19dcf0c3ccf6863c5b028b4b58c9c", - url = "mvn:org.powermock:powermock-core:jar:1.6.2", + name = "byte-buddy-binary.jar", + sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840", + url = "mvn:net.bytebuddy:byte-buddy:jar:1.9.10", ) rn_prebuilt_jar( - name = "powermock-api-mockito", - binary_jar = ":download-powermock-api-mockito.jar", + name = "byte-buddy-agent", + binary_jar = ":byte-buddy-agent-binary.jar", visibility = ["//ReactAndroid/..."], ) fb_native.remote_file( - name = "download-powermock-api-mockito.jar", - sha1 = "c213230ae20a7b422f3d622a261d0e3427d2464c", - url = "mvn:org.powermock:powermock-api-mockito:jar:1.6.2", + name = "byte-buddy-agent-binary.jar", + sha1 = "9674aba5ee793e54b864952b001166848da0f26b", + url = "mvn:net.bytebuddy:byte-buddy-agent:jar:1.9.10", ) rn_prebuilt_jar( - name = "powermock-api-support", - binary_jar = ":download-powermock-api-support.jar", + name = "objenesis", + binary_jar = ":objenesis-binary.jar", visibility = ["//ReactAndroid/..."], ) fb_native.remote_file( - name = "download-powermock-api-support.jar", - sha1 = "93b21413b4ee99b7bc0dd34e1416fdca96866aaf", - url = "mvn:org.powermock:powermock-api-support:jar:1.6.2", + name = "objenesis-binary.jar", + sha1 = "639033469776fd37c08358c6b92a4761feb2af4b", + url = "mvn:org.objenesis:objenesis:jar:2.6", ) rn_prebuilt_jar( - name = "powermock-module-junit4-rule", - binary_jar = ":download-powermock-module-junit4-rule.jar", - visibility = ["//ReactAndroid/..."], + name = "powermock-reflect-prebuilt", + binary_jar = ":powermock-reflect.jar", ) fb_native.remote_file( - name = "download-powermock-module-junit4-rule.jar", - sha1 = "4847638c5729b9f203e21144b0bdb5d34d888473", - url = "mvn:org.powermock:powermock-module-junit4-rule:jar:1.6.2", + name = "powermock-reflect.jar", + sha1 = "9a8b85397c5a72923962ee9e6bf774e8458803bb", + url = "mvn:org.powermock:powermock-reflect:jar:2.0.7", ) rn_prebuilt_jar( - name = "powermock-classloading-xstream", - binary_jar = ":download-powermock-classloading-xstream.jar", - visibility = ["//ReactAndroid/..."], + name = "powermock-api-mockito2", + binary_jar = ":powermock-api-mockito2.jar", ) fb_native.remote_file( - name = "download-powermock-classloading-xstream.jar", - sha1 = "3ced31cd7024fe365b9f3c8082d22c02434577da", - url = "mvn:org.powermock:powermock-classloading-xstream:jar:1.6.2", + name = "powermock-api-mockito2.jar", + sha1 = "9f40156d9f6f65c6459a65e34f3c7c4fef8b3c49", + url = "mvn:org.powermock:powermock-api-mockito2:jar:2.0.7", +) + +rn_prebuilt_jar( + name = "powermock-api-support", + binary_jar = ":powermock-api-support.jar", +) + +fb_native.remote_file( + name = "powermock-api-support.jar", + sha1 = "e311918de98f5d8b726031ca840664691599fd71", + url = "mvn:org.powermock:powermock-api-support:jar:2.0.7", ) rn_prebuilt_jar( name = "powermock-classloading-base", - binary_jar = ":download-powermock-classloading-base.jar", - visibility = ["//ReactAndroid/..."], + binary_jar = ":powermock-classloading-base.jar", ) fb_native.remote_file( - name = "download-powermock-classloading-base.jar", - sha1 = "c8bfc10731a02d3b241892cf2c334a754d473ca7", - url = "mvn:org.powermock:powermock-classloading-base:jar:1.6.2", + name = "powermock-classloading-base.jar", + sha1 = "58ae5d3087ddfee5a591131d337907401276f7d4", + url = "mvn:org.powermock:powermock-classloading-base:jar:2.0.7", ) rn_prebuilt_jar( - name = "xstream", - binary_jar = ":download-xstream.jar", - visibility = ["//ReactAndroid/..."], + name = "powermock-classloading-xstream-prebuilt", + binary_jar = ":powermock-classloading-xstream.jar", ) fb_native.remote_file( - name = "download-xstream.jar", - sha1 = "97e5013f391487cce4de6b0eebcde21549e91872", - url = "mvn:com.thoughtworks.xstream:xstream:jar:1.4.2", + name = "powermock-classloading-xstream.jar", + sha1 = "2ec4d94a584f12b0aa1165279e92ef3d5fda1b93", + url = "mvn:org.powermock:powermock-classloading-xstream:jar:2.0.7", ) rn_prebuilt_jar( - name = "powermock-reflect", - binary_jar = ":download-powermock-reflect.jar", - visibility = ["//ReactAndroid/..."], + name = "powermock-core", + binary_jar = ":powermock-core.jar", ) fb_native.remote_file( - name = "download-powermock-reflect.jar", - sha1 = "1af1bbd1207c3ecdcf64973e6f9d57dcd17cc145", - url = "mvn:org.powermock:powermock-reflect:jar:1.6.2", + name = "powermock-core.jar", + sha1 = "484c06b406c5a21a4a2ad39f6fe36a0f77834aa9", + url = "mvn:org.powermock:powermock-core:jar:2.0.7", ) rn_prebuilt_jar( - name = "javassist", - binary_jar = ":download-javassist.jar", - visibility = ["//ReactAndroid/..."], + name = "powermock-module-junit-common-prebuilt", + binary_jar = ":powermock-module-junit-common.jar", ) fb_native.remote_file( - name = "download-javassist.jar", - sha1 = "a9cbcdfb7e9f86fbc74d3afae65f2248bfbf82a0", - url = "mvn:org.javassist:javassist:jar:3.20.0-GA", + name = "powermock-module-junit-common.jar", + sha1 = "e890f92292aa525000a8fa95a8ca4015e3eb78b8", + url = "mvn:org.powermock:powermock-module-junit4-common:jar:2.0.7", ) rn_prebuilt_jar( - name = "mockito-all", - binary_jar = ":download-mockito-all.jar", - visibility = ["//ReactAndroid/..."], + name = "powermock-module-junit-rule-prebuilt", + binary_jar = ":powermock-module-junit-rule.jar", ) fb_native.remote_file( - name = "download-mockito-all.jar", - sha1 = "539df70269cc254a58cccc5d8e43286b4a73bf30", - url = "mvn:org.mockito:mockito-all:jar:1.10.19", + name = "powermock-module-junit-rule.jar", + sha1 = "d0d14709ffec2c3cbad0e3d6256bc8ace682398d", + url = "mvn:org.powermock:powermock-module-junit4-rule:jar:2.0.7", ) rn_prebuilt_jar( - name = "xmlpull", - binary_jar = ":download-xmlpull.jar", - visibility = ["//ReactAndroid/..."], + name = "javassist-prebuilt", + binary_jar = ":javassist.jar", ) fb_native.remote_file( - name = "download-xmlpull.jar", - sha1 = "2b8e230d2ab644e4ecaa94db7cdedbc40c805dfa", - url = "mvn:xmlpull:xmlpull:jar:1.1.3.1", + name = "javassist.jar", + sha1 = "f63e6aa899e15eca8fdaa402a79af4c417252213", + url = "mvn:org.javassist:javassist:jar:3.27.0-GA", ) rn_prebuilt_jar( - name = "xpp3", - binary_jar = ":download-xpp3.jar", - visibility = ["//ReactAndroid/..."], + name = "xstream-prebuilt", + binary_jar = ":xstream.jar", ) fb_native.remote_file( - name = "download-xpp3.jar", - sha1 = "19d4e90b43059058f6e056f794f0ea4030d60b86", - url = "mvn:xpp3:xpp3_min:jar:1.1.4c", + name = "xstream.jar", + sha1 = "6c120c45a8c480bb2fea5b56502e3993ddd74fd2", + url = "mvn:com.thoughtworks.xstream:xstream:jar:1.4.11.1", ) diff --git a/ReactAndroid/src/main/res/shell/values/styles.xml b/ReactAndroid/src/main/res/shell/values/styles.xml index b53a21ea4f4d34..b0418ce8525aab 100644 --- a/ReactAndroid/src/main/res/shell/values/styles.xml +++ b/ReactAndroid/src/main/res/shell/values/styles.xml @@ -27,21 +27,4 @@ calendar - - - - - - - - - diff --git a/ReactAndroid/src/main/third-party/android/androidx/BUCK b/ReactAndroid/src/main/third-party/android/androidx/BUCK index 1f1d33cbc103e2..2eec208a3e473f 100644 --- a/ReactAndroid/src/main/third-party/android/androidx/BUCK +++ b/ReactAndroid/src/main/third-party/android/androidx/BUCK @@ -281,6 +281,15 @@ fb_native.android_library( ], ) +fb_native.android_library( + name = "test-monitor", + visibility = ["PUBLIC"], + exported_deps = [ + ":annotation", + ":test-monitor-binary", + ], +) + fb_native.android_library( name = "vectordrawable", visibility = ["PUBLIC"], @@ -448,6 +457,11 @@ fb_native.android_prebuilt_aar( aar = ":swiperefreshlayout-binary-aar", ) +fb_native.android_prebuilt_aar( + name = "test-monitor-binary", + aar = ":test-monitor-binary-aar", +) + fb_native.android_prebuilt_aar( name = "vectordrawable-binary", aar = ":vectordrawable-binary-aar", @@ -625,6 +639,12 @@ fb_native.remote_file( url = "mvn:androidx.swiperefreshlayout:swiperefreshlayout:aar:1.0.0", ) +fb_native.remote_file( + name = "test-monitor-binary-aar", + sha1 = "d2f75d117c055f35c8ebbd4f96fabc2137df9e4d", + url = "mvn:androidx.test:monitor:aar:1.2.0", +) + fb_native.remote_file( name = "vectordrawable-binary-aar", sha1 = "33d1eb71849dffbad12add134a25eb63cad4a1eb", diff --git a/ReactAndroid/src/main/third-party/java/asm/BUCK b/ReactAndroid/src/main/third-party/java/asm/BUCK index c56db5422144ea..f344506a49ca34 100644 --- a/ReactAndroid/src/main/third-party/java/asm/BUCK +++ b/ReactAndroid/src/main/third-party/java/asm/BUCK @@ -21,8 +21,8 @@ rn_prebuilt_jar( fb_native.remote_file( name = "download-asm.jar", - sha1 = "2fd56467a018aafe6ec6a73ccba520be4a7e1565", - url = "mvn:org.ow2.asm:asm:jar:5.0.1", + sha1 = "d74d4ba0dee443f68fb2dcb7fcdb945a2cd89912", + url = "mvn:org.ow2.asm:asm:jar:7.0", ) rn_prebuilt_jar( @@ -33,8 +33,8 @@ rn_prebuilt_jar( fb_native.remote_file( name = "download-asm-commons.jar", - sha1 = "7b7147a390a93a14d2edfdcf3f7b0e87a0939c3e", - url = "mvn:org.ow2.asm:asm-commons:jar:5.0.1", + sha1 = "478006d07b7c561ae3a92ddc1829bca81ae0cdd1", + url = "mvn:org.ow2.asm:asm-commons:jar:7.0", ) rn_prebuilt_jar( @@ -45,8 +45,8 @@ rn_prebuilt_jar( fb_native.remote_file( name = "download-asm-tree.jar", - sha1 = "1b1e6e9d869acd704056d0a4223071a511c619e6", - url = "mvn:org.ow2.asm:asm-tree:jar:5.0.1", + sha1 = "29bc62dcb85573af6e62e5b2d735ef65966c4180", + url = "mvn:org.ow2.asm:asm-tree:jar:7.0", ) rn_prebuilt_jar( @@ -57,8 +57,8 @@ rn_prebuilt_jar( fb_native.remote_file( name = "download-asm-util.jar", - sha1 = "7c8caddfbd0b2d7b844f8fcc75175b9cb9cf4724", - url = "mvn:org.ow2.asm:asm-util:jar:5.0.1", + sha1 = "18d4d07010c24405129a6dbb0e92057f8779fb9d", + url = "mvn:org.ow2.asm:asm-util:jar:7.0", ) rn_prebuilt_jar( @@ -69,6 +69,6 @@ rn_prebuilt_jar( fb_native.remote_file( name = "download-asm-analysis.jar", - sha1 = "e286fbee48efacb4e7c175f7948d9d8b2ab52352", - url = "mvn:org.ow2.asm:asm-analysis:jar:5.0.1", + sha1 = "4b310d20d6f1c6b7197a75f1b5d69f169bc8ac1f", + url = "mvn:org.ow2.asm:asm-analysis:jar:7.0", ) diff --git a/ReactAndroid/src/main/third-party/java/mockito2/BUCK b/ReactAndroid/src/main/third-party/java/mockito2/BUCK new file mode 100644 index 00000000000000..2427938b973112 --- /dev/null +++ b/ReactAndroid/src/main/third-party/java/mockito2/BUCK @@ -0,0 +1,60 @@ +load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") +load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library", "rn_prebuilt_jar") + +rn_android_library( + name = "mockito2", + visibility = ["PUBLIC"], + exported_deps = [ + ":byte-buddy", + ":byte-buddy-agent", + ":mockito-core-prebuilt", + ":objenesis", + ], +) + +rn_prebuilt_jar( + name = "mockito-core-prebuilt", + binary_jar = ":mockito-core.jar", +) + +fb_native.remote_file( + name = "mockito-core.jar", + sha1 = "17fb1bf75af4f5a18d8dec73b3aa55f18e6fa21a", + url = "mvn:org.mockito:mockito-core:jar:2.26.0", +) + +rn_prebuilt_jar( + name = "byte-buddy", + binary_jar = ":byte-buddy-binary.jar", + visibility = ["//ReactAndroid/..."], +) + +fb_native.remote_file( + name = "byte-buddy-binary.jar", + sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840", + url = "mvn:net.bytebuddy:byte-buddy:jar:1.9.10", +) + +rn_prebuilt_jar( + name = "byte-buddy-agent", + binary_jar = ":byte-buddy-agent-binary.jar", + visibility = ["//ReactAndroid/..."], +) + +fb_native.remote_file( + name = "byte-buddy-agent-binary.jar", + sha1 = "9674aba5ee793e54b864952b001166848da0f26b", + url = "mvn:net.bytebuddy:byte-buddy-agent:jar:1.9.10", +) + +rn_prebuilt_jar( + name = "objenesis", + binary_jar = ":objenesis-binary.jar", + visibility = ["//ReactAndroid/..."], +) + +fb_native.remote_file( + name = "objenesis-binary.jar", + sha1 = "639033469776fd37c08358c6b92a4761feb2af4b", + url = "mvn:org.objenesis:objenesis:jar:2.6", +) diff --git a/ReactAndroid/src/main/third-party/java/robolectric/4.3.1/BUCK b/ReactAndroid/src/main/third-party/java/robolectric/4.3.1/BUCK new file mode 100644 index 00000000000000..aeca8ed4f5bd3a --- /dev/null +++ b/ReactAndroid/src/main/third-party/java/robolectric/4.3.1/BUCK @@ -0,0 +1,216 @@ +load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library", "rn_prebuilt_jar") + +rn_android_library( + name = "robolectric", + visibility = ["PUBLIC"], + exported_deps = [ + ":android-all-4.1.2_r1-robolectric-r1", + ":bouncycastle", + ":guava", + ":javax-annotation-api", + ":javax-inject", + ":robolectric4-annotations-prebuilt", + ":robolectric4-junit-prebuilt", + ":robolectric4-pluginapi-prebuilt", + ":robolectric4-plugins-maven-dependency-resolver-prebuilt", + ":robolectric4-prebuilt", + ":robolectric4-resources-prebuilt", + ":robolectric4-sandbox-prebuilt", + ":robolectric4-shadowapi-prebuilt", + ":robolectric4-shadows-framework-prebuilt", + ":robolectric4-utils-prebuilt", + ":robolectric4-utils-reflector-prebuilt", + react_native_dep("third-party/java/asm:asm"), + react_native_dep("third-party/java/sqlite:sqlite"), + react_native_dep("third-party/java/junit:junit"), + react_native_dep("third-party/android/androidx:test-monitor"), + ], +) + +rn_prebuilt_jar( + name = "android-all-4.1.2_r1-robolectric-r1", # name defines filename used by robolectric in runtime + binary_jar = ":robolectric-android-all-binary.jar", + visibility = ["//ReactAndroid/..."], +) + +# This new rule will make the .jar file appear in the "right" location, +# though that may change in the future +fb_native.export_file( + name = "robolectric-android-all-binary.jar", + src = ":robolectric-android-all-binary-remote.jar", + out = "../android-all-4.1.2_r1-robolectric-r1.jar", # name defines filename used by robolectric in runtime +) + +fb_native.remote_file( + name = "robolectric-android-all-binary-remote.jar", + sha1 = "8355a2da59fe0233ca45070ca32f08da98d0b806", + url = "mvn:org.robolectric:android-all:jar:4.1.2_r1-robolectric-r1", +) + +rn_prebuilt_jar( + name = "bouncycastle", + binary_jar = ":bouncycastle-binary.jar", + visibility = ["//ReactAndroid/..."], +) + +fb_native.remote_file( + name = "bouncycastle-binary.jar", + sha1 = "2507204241ab450456bdb8e8c0a8f986e418bd99", + url = "mvn:org.bouncycastle:bcprov-jdk15on:jar:1.59", +) + +rn_prebuilt_jar( + name = "guava", + binary_jar = ":guava-binary.jar", + visibility = ["//ReactAndroid/..."], +) + +fb_native.remote_file( + name = "guava-binary.jar", + sha1 = "ef69663836b339db335fde0df06fb3cd84e3742b", + url = "mvn:com.google.guava:guava:jar:26.0-android", +) + +rn_prebuilt_jar( + name = "robolectric4-prebuilt", + binary_jar = ":robolectric4.jar", +) + +fb_native.remote_file( + name = "robolectric4.jar", + sha1 = "66e4550b96285eadcb5a45a21ad6fbe8842fa960", + url = "mvn:org.robolectric:robolectric:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-annotations-prebuilt", + binary_jar = ":robolectric4-annotations.jar", +) + +fb_native.remote_file( + name = "robolectric4-annotations.jar", + sha1 = "3db63d633be908a18db18615b594f824c034ae6d", + url = "mvn:org.robolectric:annotations:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-junit-prebuilt", + binary_jar = ":robolectric4-junit.jar", +) + +fb_native.remote_file( + name = "robolectric4-junit.jar", + sha1 = "fcafc9942e8748c8bab832b022672ca21808c492", + url = "mvn:org.robolectric:junit:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-pluginapi-prebuilt", + binary_jar = ":robolectric4-pluginapi.jar", +) + +fb_native.remote_file( + name = "robolectric4-pluginapi.jar", + sha1 = "128acea3aed3bbe36f8fde865f3a26b920237718", + url = "mvn:org.robolectric:pluginapi:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-plugins-maven-dependency-resolver-prebuilt", + binary_jar = ":robolectric4-plugins-maven-dependency-resolver.jar", +) + +fb_native.remote_file( + name = "robolectric4-plugins-maven-dependency-resolver.jar", + sha1 = "b1ea126cb80dbba0c2947be9234bbe2877ce2a09", + url = "mvn:org.robolectric:plugins-maven-dependency-resolver:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-resources-prebuilt", + binary_jar = ":robolectric4-resources.jar", +) + +fb_native.remote_file( + name = "robolectric4-resources.jar", + sha1 = "e40030b0f6808ca378bd2c803713157ee4287ea0", + url = "mvn:org.robolectric:resources:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-sandbox-prebuilt", + binary_jar = ":robolectric4-sandbox.jar", +) + +fb_native.remote_file( + name = "robolectric4-sandbox.jar", + sha1 = "2302e406aebab5f6843dbf6c2f21952fa86ec26f", + url = "mvn:org.robolectric:sandbox:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-shadowapi-prebuilt", + binary_jar = ":robolectric4-shadowapi.jar", +) + +fb_native.remote_file( + name = "robolectric4-shadowapi.jar", + sha1 = "81dfcf4a45b623b7744e46358d01c7ce054d0fff", + url = "mvn:org.robolectric:shadowapi:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-shadows-framework-prebuilt", + binary_jar = ":robolectric4-shadows-framework.jar", +) + +fb_native.remote_file( + name = "robolectric4-shadows-framework.jar", + sha1 = "150103d5732c432906f6130b734e7452855dd67b", + url = "mvn:org.robolectric:shadows-framework:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-utils-prebuilt", + binary_jar = ":robolectric4-utils.jar", +) + +fb_native.remote_file( + name = "robolectric4-utils.jar", + sha1 = "97b0331b67d0e1dc8bf50e570b6feb017f62aed1", + url = "mvn:org.robolectric:utils:jar:4.3", +) + +rn_prebuilt_jar( + name = "robolectric4-utils-reflector-prebuilt", + binary_jar = ":robolectric4-utils-reflector.jar", +) + +fb_native.remote_file( + name = "robolectric4-utils-reflector.jar", + sha1 = "3428887d068b66e33026ac533ae4647355167658", + url = "mvn:org.robolectric:utils-reflector:jar:4.3", +) + +rn_prebuilt_jar( + name = "javax-annotation-api", + binary_jar = ":javax-annotation-api.jar", +) + +fb_native.remote_file( + name = "javax-annotation-api.jar", + sha1 = "934c04d3cfef185a8008e7bf34331b79730a9d43", + url = "mvn:javax.annotation:javax.annotation-api:jar:1.3.2", +) + +rn_prebuilt_jar( + name = "javax-inject", + binary_jar = ":javax-inject.jar", +) + +fb_native.remote_file( + name = "javax-inject.jar", + sha1 = "6975da39a7040257bd51d21a231b76c915872d38", + url = "mvn:javax.inject:javax.inject:jar:1", +) diff --git a/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/BUCK b/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/BUCK deleted file mode 100644 index 3e35f4a81a788c..00000000000000 --- a/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/BUCK +++ /dev/null @@ -1,180 +0,0 @@ -load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library", "rn_prebuilt_jar") - -rn_android_library( - name = "robolectric", - visibility = ["//ReactAndroid/..."], - exported_deps = [ - ":android-all-4.1.2_r1-robolectric-0", - ":bouncycastle", - ":icu", - ":json-20080701", - ":robolectric-annotations", - ":robolectric-core", - ":robolectric-resources", - ":robolectric-utils", - ":shadows-core-3.0-16", - ":tagsoup-1.2", - ":vtd-xml", - react_native_dep("third-party/java/asm:asm"), - react_native_dep("third-party/java/sqlite:sqlite"), - ], -) - -rn_prebuilt_jar( - name = "robolectric-core", - binary_jar = ":robolectric-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "robolectric-binary.jar", - sha1 = "f888cea3bc1a24110e315eb9827ab593610ea62f", - url = "mvn:org.robolectric:robolectric:jar:3.0", -) - -rn_prebuilt_jar( - name = "robolectric-resources", - binary_jar = ":robolectric-resources-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "robolectric-resources-binary.jar", - sha1 = "1ab609054aab67cd13a434567467f4b4774f2465", - url = "mvn:org.robolectric:robolectric-resources:jar:3.0", -) - -rn_prebuilt_jar( - name = "robolectric-annotations", - binary_jar = ":robolectric-annotations-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "robolectric-annotations-binary.jar", - sha1 = "2a6cfc072d7680694c1ff893c5dc8fec33163110", - url = "mvn:org.robolectric:robolectric-annotations:jar:3.0", -) - -rn_prebuilt_jar( - name = "robolectric-utils", - binary_jar = ":robolectric-utils-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "robolectric-utils-binary.jar", - sha1 = "4bcecd8115fe7296088bb1636e6cbd7ae8927392", - url = "mvn:org.robolectric:robolectric-utils:jar:3.0", -) - -rn_prebuilt_jar( - name = "bouncycastle", - binary_jar = ":bouncycastle-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "bouncycastle-binary.jar", - sha1 = "2507204241ab450456bdb8e8c0a8f986e418bd99", - url = "mvn:org.bouncycastle:bcprov-jdk15on:jar:1.59", -) - -rn_prebuilt_jar( - name = "vtd-xml", - binary_jar = ":vtd-xml-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "vtd-xml-binary.jar", - sha1 = "ee5bcf62c1acf76434ee9f1c67a840bafef72a6d", - url = "mvn:com.ximpleware:vtd-xml:jar:2.11", -) - -rn_prebuilt_jar( - name = "icu", - binary_jar = ":icu-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.remote_file( - name = "icu-binary.jar", - sha1 = "786d9055d4ca8c1aab4a7d4ac8283f973fd7e41f", - url = "mvn:com.ibm.icu:icu4j:jar:53.1", -) - -rn_prebuilt_jar( - name = "android-all-4.1.2_r1-robolectric-0", # name defines filename used by robolectric in runtime - binary_jar = ":robolectric-android-all-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -# This new rule will make the .jar file appear in the "right" location, -# though that may change in the future -fb_native.export_file( - name = "robolectric-android-all-binary.jar", - src = ":robolectric-android-all-binary-remote.jar", - out = "../android-all-4.1.2_r1-robolectric-0.jar", # name defines filename used by robolectric in runtime -) - -fb_native.remote_file( - name = "robolectric-android-all-binary-remote.jar", - sha1 = "aecc8ce5119a25fcea1cdf8285469c9d1261a352", - url = "mvn:org.robolectric:android-all:jar:4.1.2_r1-robolectric-0", -) - -rn_prebuilt_jar( - name = "json-20080701", # name defines filename used by robolectric in runtime - binary_jar = ":json.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.export_file( - name = "json.jar", - src = ":json-remote.jar", - out = "../json-20080701.jar", # name defines filename used by robolectric in runtime -) - -fb_native.remote_file( - name = "json-remote.jar", - sha1 = "d652f102185530c93b66158b1859f35d45687258", - url = "mvn:org.json:json:jar:20080701", -) - -rn_prebuilt_jar( - name = "tagsoup-1.2", # name defines filename used by robolectric in runtime - binary_jar = ":tagsoup.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.export_file( - name = "tagsoup.jar", - src = ":tagsoup-remote.jar", - out = "../tagsoup-1.2.jar", # name defines filename used by robolectric in runtime -) - -fb_native.remote_file( - name = "tagsoup-remote.jar", - sha1 = "639fd364750d7363c85797dc944b4a80f78fa684", - url = "mvn:org.ccil.cowan.tagsoup:tagsoup:jar:1.2", -) - -rn_prebuilt_jar( - name = "shadows-core-3.0-16", # name defines filename used by robolectric in runtime - binary_jar = ":robolectric-shadows-binary.jar", - visibility = ["//ReactAndroid/..."], -) - -fb_native.export_file( - name = "robolectric-shadows-binary.jar", - src = ":robolectric-shadows-binary-remote.jar", - out = "../shadows-core-3.0-16.jar", # name defines filename used by robolectric in runtime -) - -fb_native.remote_file( - name = "robolectric-shadows-binary-remote.jar", - sha1 = "39d7a856bf91640b1a6d044333336a2b3f3c198f", - url = "https://repo1.maven.org/maven2/org/robolectric/shadows-core/3.0/shadows-core-3.0-16.jar", -) diff --git a/ReactAndroid/src/test/java/com/facebook/react/BUCK b/ReactAndroid/src/test/java/com/facebook/react/BUCK index 3b431158943706..036fe8a98d8bfb 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/BUCK @@ -6,7 +6,6 @@ rn_robolectric_test( contacts = ["oncall+react_native@xmail.facebook.com"], deps = [ YOGA_TARGET, - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), react_native_dep("third-party/android/androidx:fragment"), @@ -15,10 +14,8 @@ rn_robolectric_test( react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okio:okio"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK b/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK index 76d35a6ce1c914..c62728d5814eb9 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/BUCK @@ -9,12 +9,9 @@ rn_robolectric_test( "PUBLIC", ], deps = [ - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/animated:animated"), react_native_target("java/com/facebook/react/bridge:bridge"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK b/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK index 1f5f367286672e..2721f9cb923f4a 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK @@ -1,4 +1,4 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "react_native_tests_target", "rn_android_library", "rn_robolectric_test") +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_android_toplevel_dep", "react_native_dep", "react_native_target", "react_native_tests_target", "rn_android_library", "rn_robolectric_test") STANDARD_TEST_SRCS = [ "*Test.java", @@ -14,8 +14,8 @@ rn_android_library( "PUBLIC", ], deps = [ - react_native_dep("third-party/java/mockito:mockito"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), + react_native_android_toplevel_dep("third-party/java/mockito2:mockito2"), + react_native_dep("third-party/java/robolectric/4.3.1:robolectric"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/uimanager:uimanager"), react_native_tests_target("java/org/mockito/configuration:configuration"), @@ -31,13 +31,10 @@ rn_robolectric_test( ], deps = [ ":testhelpers", - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"), react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/uimanager:uimanager"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java index 77a46c9fee4d63..d5b89b2465147c 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java @@ -7,13 +7,14 @@ package com.facebook.react.bridge; +import static org.mockito.Mockito.when; + import com.facebook.soloader.SoLoader; import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; @@ -56,14 +57,14 @@ private int findMethod(String mname, List me @Test(expected = NativeArgumentsParseException.class) public void testCallMethodWithoutEnoughArgs() throws Exception { int methodId = findMethod("regularMethod", mMethods); - Mockito.stub(mArguments.size()).toReturn(1); + when(mArguments.size()).thenReturn(1); mWrapper.invoke(methodId, mArguments); } @Test public void testCallMethodWithEnoughArgs() { int methodId = findMethod("regularMethod", mMethods); - Mockito.stub(mArguments.size()).toReturn(2); + when(mArguments.size()).thenReturn(2); mWrapper.invoke(methodId, mArguments); } @@ -71,14 +72,14 @@ public void testCallMethodWithEnoughArgs() { public void testCallAsyncMethodWithEnoughArgs() { // Promise block evaluates to 2 args needing to be passed from JS int methodId = findMethod("asyncMethod", mMethods); - Mockito.stub(mArguments.size()).toReturn(3); + when(mArguments.size()).thenReturn(3); mWrapper.invoke(methodId, mArguments); } @Test public void testCallSyncMethod() { int methodId = findMethod("syncMethod", mMethods); - Mockito.stub(mArguments.size()).toReturn(2); + when(mArguments.size()).thenReturn(2); mWrapper.invoke(methodId, mArguments); } diff --git a/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK b/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK index 2bf74a64315a33..f720bc4aaf3f4b 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK @@ -9,14 +9,11 @@ rn_robolectric_test( "PUBLIC", ], deps = [ - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okio:okio"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java b/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java index 0c6bbde0e8bdd0..f155eebbddd312 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/devsupport/JSDebuggerWebSocketClientTest.java @@ -76,7 +76,7 @@ public void test_onMessage_WithInvalidContentType_ShouldNotTriggerCallbacks() th client.onMessage(null, ByteString.encodeUtf8("{\"replyID\":0, \"result\":\"OK\"}")); PowerMockito.verifyPrivate(client, never()) - .invoke("triggerRequestSuccess", anyInt(), anyString()); + .invoke("triggerRequestSuccess", anyInt(), nullable(String.class)); PowerMockito.verifyPrivate(client, never()).invoke("triggerRequestFailure", anyInt(), any()); } @@ -86,7 +86,7 @@ public void test_onMessage_WithoutReplyId_ShouldNotTriggerCallbacks() throws Exc client.onMessage(null, "{\"result\":\"OK\"}"); PowerMockito.verifyPrivate(client, never()) - .invoke("triggerRequestSuccess", anyInt(), anyString()); + .invoke("triggerRequestSuccess", anyInt(), nullable(String.class)); PowerMockito.verifyPrivate(client, never()).invoke("triggerRequestFailure", anyInt(), any()); } @@ -96,7 +96,7 @@ public void test_onMessage_With_Null_ReplyId_ShouldNotTriggerCallbacks() throws client.onMessage(null, "{\"replyID\":null, \"result\":\"OK\"}"); PowerMockito.verifyPrivate(client, never()) - .invoke("triggerRequestSuccess", anyInt(), anyString()); + .invoke("triggerRequestSuccess", anyInt(), nullable(String.class)); PowerMockito.verifyPrivate(client, never()).invoke("triggerRequestFailure", anyInt(), any()); } @@ -131,6 +131,7 @@ public void test_onMessage_With_Null_Error_ShouldTriggerRequestSuccess() throws JSDebuggerWebSocketClient client = PowerMockito.spy(new JSDebuggerWebSocketClient()); client.onMessage(null, "{\"replyID\":0, \"error\":null}"); - PowerMockito.verifyPrivate(client).invoke("triggerRequestSuccess", anyInt(), anyString()); + PowerMockito.verifyPrivate(client) + .invoke("triggerRequestSuccess", anyInt(), nullable(String.class)); } } diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK index 3b5337ebbb64f2..750600054fbf23 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK @@ -9,7 +9,6 @@ rn_robolectric_test( ], deps = [ YOGA_TARGET, - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), react_native_dep("third-party/android/androidx:fragment"), @@ -18,10 +17,8 @@ rn_robolectric_test( react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okio:okio"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java index b46488d299d486..619b464d1d7c0b 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java @@ -25,7 +25,7 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.util.ActivityController; +import org.robolectric.android.controller.ActivityController; @RunWith(RobolectricTestRunner.class) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java index 36104c1e285e87..a6e401ed9b0c8c 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java @@ -518,9 +518,9 @@ public Object answer(InvocationOnMock invocation) throws Throwable { /* withCredentials */ false); // verify RequestBodyPart for image - PowerMockito.verifyStatic(times(1)); + PowerMockito.verifyStatic(RequestBodyUtil.class, times(1)); RequestBodyUtil.getFileInputStream(any(ReactContext.class), eq("imageUri")); - PowerMockito.verifyStatic(times(1)); + PowerMockito.verifyStatic(RequestBodyUtil.class, times(1)); RequestBodyUtil.create(MediaType.parse("image/jpg"), inputStream); // verify body @@ -579,7 +579,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { verify(mHttpClient, times(3)).newCall(any(Request.class)); mNetworkingModule.onCatalystInstanceDestroy(); - PowerMockito.verifyStatic(times(3)); + PowerMockito.verifyStatic(OkHttpCallUtil.class, times(3)); ArgumentCaptor clientArguments = ArgumentCaptor.forClass(OkHttpClient.class); ArgumentCaptor requestIdArguments = ArgumentCaptor.forClass(Integer.class); OkHttpCallUtil.cancelTag(clientArguments.capture(), requestIdArguments.capture()); @@ -624,7 +624,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { verify(mHttpClient, times(3)).newCall(any(Request.class)); mNetworkingModule.abortRequest(requests); - PowerMockito.verifyStatic(times(1)); + PowerMockito.verifyStatic(OkHttpCallUtil.class, times(1)); ArgumentCaptor clientArguments = ArgumentCaptor.forClass(OkHttpClient.class); ArgumentCaptor requestIdArguments = ArgumentCaptor.forClass(Integer.class); OkHttpCallUtil.cancelTag(clientArguments.capture(), requestIdArguments.capture()); @@ -635,7 +635,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { // If `cancelTag` would've been called again for the aborted call, we would have had // `requests + 1` calls. mNetworkingModule.onCatalystInstanceDestroy(); - PowerMockito.verifyStatic(times(requests)); + PowerMockito.verifyStatic(OkHttpCallUtil.class, times(requests)); clientArguments = ArgumentCaptor.forClass(OkHttpClient.class); requestIdArguments = ArgumentCaptor.forClass(Integer.class); OkHttpCallUtil.cancelTag(clientArguments.capture(), requestIdArguments.capture()); diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java index 97f3a48171c891..ef21b78fe94019 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/ReactCookieJarContainerTest.java @@ -32,7 +32,8 @@ public class ReactCookieJarContainerTest { @Test public void testMissingJar() throws Exception { ReactCookieJarContainer jarContainer = mock(ReactCookieJarContainer.class); - assertThat(jarContainer.loadForRequest(any(HttpUrl.class)).size()).isEqualTo(0); + assertThat(jarContainer.loadForRequest(HttpUrl.parse("http://example.com")).size()) + .isEqualTo(0); } @Test @@ -40,7 +41,8 @@ public void testEmptyCookies() throws Exception { ReactCookieJarContainer jarContainer = mock(ReactCookieJarContainer.class); List cookies = new ArrayList<>(); when(jarContainer.loadForRequest(any(HttpUrl.class))).thenReturn(cookies); - assertThat(jarContainer.loadForRequest(any(HttpUrl.class)).size()).isEqualTo(0); + assertThat(jarContainer.loadForRequest(HttpUrl.parse("http://example.com")).size()) + .isEqualTo(0); } @Test @@ -51,7 +53,8 @@ public void testValidCookies() throws Exception { List cookies = new ArrayList<>(); cookies.add(new Cookie.Builder().name("valid").value("valid value").domain("domain").build()); when(cookieJar.loadForRequest(any(HttpUrl.class))).thenReturn(cookies); - assertThat(jarContainer.loadForRequest(any(HttpUrl.class)).size()).isEqualTo(1); + assertThat(jarContainer.loadForRequest(HttpUrl.parse("http://example.com")).size()) + .isEqualTo(1); } @Test @@ -62,6 +65,7 @@ public void testInvalidCookies() throws Exception { List cookies = new ArrayList<>(); cookies.add(new Cookie.Builder().name("valid").value("înválíd välÅ«Ä—").domain("domain").build()); when(cookieJar.loadForRequest(any(HttpUrl.class))).thenReturn(cookies); - assertThat(jarContainer.loadForRequest(any(HttpUrl.class)).size()).isEqualTo(0); + assertThat(jarContainer.loadForRequest(HttpUrl.parse("http://example.com")).size()) + .isEqualTo(0); } } diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java index 75a2135c223e31..2260bc47dde664 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/share/ShareModuleTest.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.robolectric.Shadows.shadowOf; import android.app.Activity; import android.content.Intent; @@ -17,6 +18,7 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactTestHelper; import com.facebook.react.bridge.WritableMap; import org.junit.After; @@ -31,14 +33,23 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.internal.ShadowExtractor; -import org.robolectric.shadows.ShadowApplication; @PrepareForTest({Arguments.class}) @RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) +@PowerMockIgnore({ + "org.mockito.*", + "org.robolectric.*", + "androidx.*", + "android.*", + "javax.xml.*", + "org.xml.sax.*", + "org.w3c.dom.*", + "org.springframework.context.*", + "org.apache.log4j.*" +}) public class ShareModuleTest { private Activity mActivity; @@ -58,7 +69,11 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }); - mShareModule = new ShareModule(ReactTestHelper.createCatalystContextForTest()); + mActivity = Robolectric.setupActivity(Activity.class); + + ReactApplicationContext applicationContext = ReactTestHelper.createCatalystContextForTest(); + applicationContext.onNewIntent(mActivity, new Intent()); + mShareModule = new ShareModule(applicationContext); } @After @@ -81,9 +96,7 @@ public void testShareDialog() { mShareModule.share(content, dialogTitle, promise); - final Intent chooserIntent = - ((ShadowApplication) ShadowExtractor.extract(RuntimeEnvironment.application)) - .getNextStartedActivity(); + final Intent chooserIntent = shadowOf(RuntimeEnvironment.application).getNextStartedActivity(); assertNotNull("Dialog was not displayed", chooserIntent); assertEquals(Intent.ACTION_CHOOSER, chooserIntent.getAction()); assertEquals(dialogTitle, chooserIntent.getExtras().get(Intent.EXTRA_TITLE)); diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java index 45b3f1b0f36d1b..bc5d9bc332649d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java @@ -36,7 +36,7 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.concurrent.RoboExecutorService; +import org.robolectric.android.util.concurrent.RoboExecutorService; /** Tests for {@link com.facebook.react.modules.storage.AsyncStorageModule}. */ @PrepareForTest({Arguments.class}) diff --git a/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/BUCK b/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/BUCK index 18dec467c72f2c..f34e2c12e0e360 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/BUCK @@ -9,14 +9,11 @@ rn_robolectric_test( "PUBLIC", ], deps = [ - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okio:okio"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react/packagerconnection:packagerconnection"), ], ) diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK index ad592a0dddf576..bc96fa72a28aa6 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK @@ -17,7 +17,6 @@ rn_robolectric_test( ], deps = [ YOGA_TARGET, - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/android/androidx:annotation"), react_native_dep("third-party/android/androidx:core"), react_native_dep("third-party/android/androidx:fragment"), @@ -26,10 +25,8 @@ rn_robolectric_test( react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okio:okio"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/layoutanimation/BUCK b/ReactAndroid/src/test/java/com/facebook/react/uimanager/layoutanimation/BUCK index 6c791141d5435f..3124e8db34698e 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/layoutanimation/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/layoutanimation/BUCK @@ -11,7 +11,6 @@ rn_robolectric_test( YOGA_TARGET, react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react/uimanager:uimanager"), ], ) diff --git a/ReactAndroid/src/test/java/com/facebook/react/util/BUCK b/ReactAndroid/src/test/java/com/facebook/react/util/BUCK index 12a2a521fc5be3..853b0a8a2f90bb 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/util/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/util/BUCK @@ -7,12 +7,9 @@ rn_robolectric_test( "PUBLIC", ], deps = [ - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react/util:util"), react_native_target("java/com/facebook/react/bridge:bridge"), ], diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK index 5b616f6630bdc9..058829726fbfa4 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK @@ -9,7 +9,6 @@ rn_robolectric_test( contacts = ["oncall+fbandroid_sheriff@xmail.facebook.com"], deps = [ YOGA_TARGET, - react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), react_native_dep("libraries/fresco/fresco-react-native:fresco-drawee"), react_native_dep("libraries/fresco/fresco-react-native:fresco-react-native"), react_native_dep("libraries/fresco/fresco-react-native:imagepipeline"), @@ -22,10 +21,8 @@ rn_robolectric_test( react_native_dep("third-party/java/fest:fest"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), - react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okio:okio"), - react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), diff --git a/ReactAndroid/src/test/java/org/mockito/configuration/BUCK b/ReactAndroid/src/test/java/org/mockito/configuration/BUCK index 99bce2a385eb49..a29174b85a037e 100644 --- a/ReactAndroid/src/test/java/org/mockito/configuration/BUCK +++ b/ReactAndroid/src/test/java/org/mockito/configuration/BUCK @@ -1,4 +1,4 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library") +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_android_toplevel_dep", "rn_android_library") rn_android_library( name = "configuration", @@ -7,6 +7,6 @@ rn_android_library( "PUBLIC", ], deps = [ - react_native_dep("third-party/java/mockito:mockito"), + react_native_android_toplevel_dep("third-party/java/mockito2:mockito2"), ], ) diff --git a/ReactCommon/ReactCommon.podspec b/ReactCommon/ReactCommon.podspec index 82b42b6c0a2280..ce62a7cfb9a539 100644 --- a/ReactCommon/ReactCommon.podspec +++ b/ReactCommon/ReactCommon.podspec @@ -38,6 +38,7 @@ Pod::Spec.new do |s| s.subspec "turbomodule" do |ss| ss.dependency "React-callinvoker", version + ss.dependency "React-perflogger", version ss.dependency "React-Core", version ss.dependency "React-cxxreact", version ss.dependency "React-jsi", version diff --git a/ReactCommon/better/BUCK b/ReactCommon/better/BUCK index fb403d3a8dfad5..53358d1a55679f 100644 --- a/ReactCommon/better/BUCK +++ b/ReactCommon/better/BUCK @@ -35,11 +35,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/callinvoker/BUCK b/ReactCommon/callinvoker/BUCK index 0f702cb7282867..b050d1f025424a 100644 --- a/ReactCommon/callinvoker/BUCK +++ b/ReactCommon/callinvoker/BUCK @@ -16,8 +16,7 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE), preferred_linkage = "static", preprocessor_flags = [ diff --git a/ReactCommon/config/BUCK b/ReactCommon/config/BUCK index 9c81d30e98dab6..9640ee45c1a529 100644 --- a/ReactCommon/config/BUCK +++ b/ReactCommon/config/BUCK @@ -24,11 +24,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/cxxreact/Android.mk b/ReactCommon/cxxreact/Android.mk index df9b9e640bc69c..a5024364c6088d 100644 --- a/ReactCommon/cxxreact/Android.mk +++ b/ReactCommon/cxxreact/Android.mk @@ -19,7 +19,7 @@ LOCAL_CFLAGS := \ LOCAL_CFLAGS += -fexceptions -frtti -Wno-unused-lambda-capture -LOCAL_STATIC_LIBRARIES := boost jsi callinvoker runtimeexecutor +LOCAL_STATIC_LIBRARIES := boost jsi callinvoker reactperflogger runtimeexecutor LOCAL_SHARED_LIBRARIES := jsinspector libfolly_json glog include $(BUILD_STATIC_LIBRARY) @@ -27,6 +27,7 @@ include $(BUILD_STATIC_LIBRARY) $(call import-module,fb) $(call import-module,folly) $(call import-module,callinvoker) +$(call import-module,reactperflogger) $(call import-module,jsc) $(call import-module,glog) $(call import-module,jsi) diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index d5453556bd6e91..36d242b889c7d2 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -18,10 +18,9 @@ rn_xplat_cxx_library( prefix = "cxxreact", ), compiler_flags = CXX_LIBRARY_COMPILER_FLAGS, - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = get_apple_compiler_flags(), - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], @@ -44,10 +43,9 @@ rn_xplat_cxx_library( "-fexceptions", "-frtti", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = get_apple_compiler_flags(), - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], preprocessor_flags = [ "-DWITH_FBREMAP=1", ], @@ -71,6 +69,7 @@ rn_xplat_cxx_library( "-fexceptions", ], fbobjc_compiler_flags = get_apple_compiler_flags(), + labels = ["supermodule:xplat/default/public.react_native.infra"], soname = "libxplat_react_module_samplemodule.$(ext)", visibility = [ "PUBLIC", @@ -128,12 +127,11 @@ rn_xplat_cxx_library( "-fexceptions", "-frtti", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_preprocessor_flags = get_android_inspector_flags(), fbobjc_compiler_flags = get_apple_compiler_flags(), fbobjc_force_static = True, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE), preprocessor_flags = [ @@ -155,6 +153,7 @@ rn_xplat_cxx_library( react_native_xplat_target("jsinspector:jsinspector"), react_native_xplat_target("microprofiler:microprofiler"), react_native_xplat_target("runtimeexecutor:runtimeexecutor"), + react_native_xplat_target("reactperflogger:reactperflogger"), "//third-party/glog:glog", "//xplat/folly:optional", ], diff --git a/ReactCommon/cxxreact/CxxNativeModule.cpp b/ReactCommon/cxxreact/CxxNativeModule.cpp index 94ab82bfe0bdad..bfbb608553b525 100644 --- a/ReactCommon/cxxreact/CxxNativeModule.cpp +++ b/ReactCommon/cxxreact/CxxNativeModule.cpp @@ -58,6 +58,18 @@ std::string CxxNativeModule::getName() { return name_; } +std::string CxxNativeModule::getSyncMethodName(unsigned int reactMethodId) { + if (reactMethodId >= methods_.size()) { + throw std::invalid_argument(folly::to( + "methodId ", + reactMethodId, + " out of range [0..", + methods_.size(), + "]")); + } + return methods_[reactMethodId].name; +} + std::vector CxxNativeModule::getMethods() { lazyInit(); diff --git a/ReactCommon/cxxreact/CxxNativeModule.h b/ReactCommon/cxxreact/CxxNativeModule.h index 2c2230409d1331..9d730ff7942470 100644 --- a/ReactCommon/cxxreact/CxxNativeModule.h +++ b/ReactCommon/cxxreact/CxxNativeModule.h @@ -37,6 +37,7 @@ class RN_EXPORT CxxNativeModule : public NativeModule { messageQueueThread_(messageQueueThread) {} std::string getName() override; + std::string getSyncMethodName(unsigned int methodId) override; std::vector getMethods() override; folly::dynamic getConstants() override; void invoke(unsigned int reactMethodId, folly::dynamic &¶ms, int callId) diff --git a/ReactCommon/cxxreact/ModuleRegistry.cpp b/ReactCommon/cxxreact/ModuleRegistry.cpp index b59dac82f44f5c..da75bc5b5857f3 100644 --- a/ReactCommon/cxxreact/ModuleRegistry.cpp +++ b/ReactCommon/cxxreact/ModuleRegistry.cpp @@ -8,6 +8,7 @@ #include "ModuleRegistry.h" #include +#include #include "NativeModule.h" #include "SystraceSection.h" @@ -99,14 +100,37 @@ folly::Optional ModuleRegistry::getConfig( if (it == modulesByName_.end()) { if (unknownModules_.find(name) != unknownModules_.end()) { + BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str()); + BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str()); return folly::none; } - if (!moduleNotFoundCallback_ || !moduleNotFoundCallback_(name) || - (it = modulesByName_.find(name)) == modulesByName_.end()) { + + if (!moduleNotFoundCallback_) { unknownModules_.insert(name); + BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str()); + BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str()); return folly::none; } + + BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str()); + + bool wasModuleLazilyLoaded = moduleNotFoundCallback_(name); + it = modulesByName_.find(name); + + bool wasModuleRegisteredWithRegistry = + wasModuleLazilyLoaded && it != modulesByName_.end(); + + if (!wasModuleRegisteredWithRegistry) { + BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str()); + unknownModules_.insert(name); + return folly::none; + } + } else { + BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str()); } + + // If we've gotten this far, then we've signaled moduleJSRequireBeginningEnd + size_t index = it->second; CHECK(index < modules_.size()); @@ -118,6 +142,12 @@ folly::Optional ModuleRegistry::getConfig( { SystraceSection s_("ModuleRegistry::getConstants", "module", name); + /** + * In the case that there are constants, we'll initialize the NativeModule, + * and signal moduleJSRequireEndingStart. Otherwise, we'll simply signal the + * event. The Module will be initialized when we invoke one of its + * NativeModule methods. + */ config.push_back(module->getConstants()); } @@ -158,6 +188,26 @@ folly::Optional ModuleRegistry::getConfig( } } +std::string ModuleRegistry::getModuleName(unsigned int moduleId) { + if (moduleId >= modules_.size()) { + throw std::runtime_error(folly::to( + "moduleId ", moduleId, " out of range [0..", modules_.size(), ")")); + } + + return modules_[moduleId]->getName(); +} + +std::string ModuleRegistry::getModuleSyncMethodName( + unsigned int moduleId, + unsigned int methodId) { + if (moduleId >= modules_.size()) { + throw std::runtime_error(folly::to( + "moduleId ", moduleId, " out of range [0..", modules_.size(), ")")); + } + + return modules_[moduleId]->getSyncMethodName(methodId); +} + void ModuleRegistry::callNativeMethod( unsigned int moduleId, unsigned int methodId, diff --git a/ReactCommon/cxxreact/ModuleRegistry.h b/ReactCommon/cxxreact/ModuleRegistry.h index b24f5390e30f46..fba4496a621734 100644 --- a/ReactCommon/cxxreact/ModuleRegistry.h +++ b/ReactCommon/cxxreact/ModuleRegistry.h @@ -59,6 +59,11 @@ class RN_EXPORT ModuleRegistry { unsigned int methodId, folly::dynamic &&args); + std::string getModuleName(unsigned int moduleId); + std::string getModuleSyncMethodName( + unsigned int moduleId, + unsigned int methodName); + private: // This is always populated std::vector> modules_; diff --git a/ReactCommon/cxxreact/NativeModule.h b/ReactCommon/cxxreact/NativeModule.h index 6e7287cf469c88..2441953b4a040b 100644 --- a/ReactCommon/cxxreact/NativeModule.h +++ b/ReactCommon/cxxreact/NativeModule.h @@ -31,6 +31,7 @@ class NativeModule { public: virtual ~NativeModule() {} virtual std::string getName() = 0; + virtual std::string getSyncMethodName(unsigned int methodId) = 0; virtual std::vector getMethods() = 0; virtual folly::dynamic getConstants() = 0; virtual void diff --git a/ReactCommon/cxxreact/NativeToJsBridge.cpp b/ReactCommon/cxxreact/NativeToJsBridge.cpp index 64b0673423b8d9..45651ac12d0bad 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.cpp +++ b/ReactCommon/cxxreact/NativeToJsBridge.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "Instance.h" #include "JSBigString.h" @@ -55,10 +56,14 @@ class JsToNativeBridge : public react::ExecutorDelegate { m_batchHadNativeModuleOrTurboModuleCalls = m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty(); + std::vector methodCalls = parseMethodCalls(std::move(calls)); + BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessEnd( + (int)methodCalls.size()); + // An exception anywhere in here stops processing of the batch. This // was the behavior of the Android bridge, and since exception handling // terminates the whole bridge, there's not much point in continuing. - for (auto &call : parseMethodCalls(std::move(calls))) { + for (auto &call : methodCalls) { m_registry->callNativeMethod( call.moduleId, call.methodId, std::move(call.arguments), call.callId); } diff --git a/ReactCommon/cxxreact/React-cxxreact.podspec b/ReactCommon/cxxreact/React-cxxreact.podspec index cdb34aa745a8ef..bea5ad31d35596 100644 --- a/ReactCommon/cxxreact/React-cxxreact.podspec +++ b/ReactCommon/cxxreact/React-cxxreact.podspec @@ -1,3 +1,4 @@ +# coding: utf-8 # Copyright (c) Facebook, Inc. and its affiliates. # # This source code is licensed under the MIT license found in the @@ -42,4 +43,5 @@ Pod::Spec.new do |s| s.dependency "React-jsinspector", version s.dependency "React-callinvoker", version s.dependency "React-runtimeexecutor", version + s.dependency "React-perflogger", version end diff --git a/ReactCommon/fabric/animations/BUCK b/ReactCommon/fabric/animations/BUCK new file mode 100644 index 00000000000000..123fb6c45e2386 --- /dev/null +++ b/ReactCommon/fabric/animations/BUCK @@ -0,0 +1,96 @@ +load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode") +load( + "//tools/build_defs/oss:rn_defs.bzl", + "ANDROID", + "APPLE", + "CXX", + "fb_xplat_cxx_test", + "get_apple_compiler_flags", + "get_apple_inspector_flags", + "react_native_xplat_target", + "rn_xplat_cxx_library", + "subdir_glob", +) + +APPLE_COMPILER_FLAGS = get_apple_compiler_flags() + +rn_xplat_cxx_library( + name = "animations", + srcs = glob( + ["**/*.cpp"], + exclude = glob(["tests/**/*.cpp"]), + ), + headers = glob( + ["**/*.h"], + exclude = glob(["tests/**/*.h"]), + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "*.h"), + ], + prefix = "react/animations", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + "-lm", + ], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], + macosx_tests_override = [], + platforms = (ANDROID, APPLE, CXX), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + tests = [":tests"], + visibility = ["PUBLIC"], + deps = [ + "//third-party/glog:glog", + "//xplat/fbsystrace:fbsystrace", + "//xplat/folly:headers_only", + "//xplat/folly:memory", + "//xplat/folly:molly", + "//xplat/jsi:JSIDynamic", + "//xplat/jsi:jsi", + react_native_xplat_target("config:config"), + react_native_xplat_target("fabric/componentregistry:componentregistry"), + react_native_xplat_target("fabric/components/view:view"), + react_native_xplat_target("fabric/core:core"), + react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("fabric/mounting:mounting"), + react_native_xplat_target("fabric/uimanager:uimanager"), + react_native_xplat_target("runtimeexecutor:runtimeexecutor"), + ], +) + +fb_xplat_cxx_test( + name = "tests", + srcs = glob(["tests/**/*.cpp"]), + headers = glob(["tests/**/*.h"]), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + platforms = (ANDROID, APPLE, CXX), + deps = [ + ":animations", + "//xplat/folly:molly", + "//xplat/third-party/gmock:gtest", + react_native_xplat_target("config:config"), + react_native_xplat_target("fabric/components/activityindicator:activityindicator"), + react_native_xplat_target("fabric/components/image:image"), + react_native_xplat_target("fabric/components/root:root"), + react_native_xplat_target("fabric/components/scrollview:scrollview"), + react_native_xplat_target("fabric/components/view:view"), + "//xplat/js/react-native-github:generated_components-rncore", + ], +) diff --git a/ReactCommon/fabric/animations/LayoutAnimationDriver.cpp b/ReactCommon/fabric/animations/LayoutAnimationDriver.cpp new file mode 100644 index 00000000000000..a1e1da95644aef --- /dev/null +++ b/ReactCommon/fabric/animations/LayoutAnimationDriver.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LayoutAnimationDriver.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace facebook { +namespace react { + +static double +getProgressFromValues(double start, double end, double currentValue) { + auto opacityMinmax = std::minmax({start, end}); + auto min = opacityMinmax.first; + auto max = opacityMinmax.second; + return ( + currentValue < min + ? 0 + : (currentValue > max ? 0 : ((max - currentValue) / (max - min)))); +} + +/** + * Given an animation and a ShadowView with properties set on it, detect how + * far through the animation the ShadowView has progressed. + * + * @param mutationsList + * @param now + */ +double LayoutAnimationDriver::getProgressThroughAnimation( + AnimationKeyFrame const &keyFrame, + LayoutAnimation const *layoutAnimation, + ShadowView const &animationStateView) const { + auto layoutAnimationConfig = layoutAnimation->layoutAnimationConfig; + auto const mutationConfig = + *(keyFrame.type == AnimationConfigurationType::Delete + ? layoutAnimationConfig.deleteConfig + : (keyFrame.type == AnimationConfigurationType::Create + ? layoutAnimationConfig.createConfig + : layoutAnimationConfig.updateConfig)); + + auto initialProps = keyFrame.viewStart.props; + auto finalProps = keyFrame.viewEnd.props; + + if (mutationConfig.animationProperty == AnimationProperty::Opacity) { + // Detect progress through opacity animation. + const auto &oldViewProps = + dynamic_cast(initialProps.get()); + const auto &newViewProps = + dynamic_cast(finalProps.get()); + const auto &animationStateViewProps = + dynamic_cast(animationStateView.props.get()); + if (oldViewProps != nullptr && newViewProps != nullptr && + animationStateViewProps != nullptr) { + return getProgressFromValues( + oldViewProps->opacity, + newViewProps->opacity, + animationStateViewProps->opacity); + } + } else if ( + mutationConfig.animationProperty != AnimationProperty::NotApplicable) { + // Detect progress through layout animation. + LayoutMetrics const &finalLayoutMetrics = keyFrame.viewEnd.layoutMetrics; + LayoutMetrics const &baselineLayoutMetrics = + keyFrame.viewStart.layoutMetrics; + LayoutMetrics const &animationStateLayoutMetrics = + animationStateView.layoutMetrics; + + if (baselineLayoutMetrics.frame.size.height != + finalLayoutMetrics.frame.size.height) { + return getProgressFromValues( + baselineLayoutMetrics.frame.size.height, + finalLayoutMetrics.frame.size.height, + animationStateLayoutMetrics.frame.size.height); + } + if (baselineLayoutMetrics.frame.size.width != + finalLayoutMetrics.frame.size.width) { + return getProgressFromValues( + baselineLayoutMetrics.frame.size.width, + finalLayoutMetrics.frame.size.width, + animationStateLayoutMetrics.frame.size.width); + } + if (baselineLayoutMetrics.frame.origin.x != + finalLayoutMetrics.frame.origin.x) { + return getProgressFromValues( + baselineLayoutMetrics.frame.origin.x, + finalLayoutMetrics.frame.origin.x, + animationStateLayoutMetrics.frame.origin.x); + } + if (baselineLayoutMetrics.frame.origin.y != + finalLayoutMetrics.frame.origin.y) { + return getProgressFromValues( + baselineLayoutMetrics.frame.origin.y, + finalLayoutMetrics.frame.origin.y, + animationStateLayoutMetrics.frame.origin.y); + } + } + + return 0; +} + +void LayoutAnimationDriver::animationMutationsForFrame( + SurfaceId surfaceId, + ShadowViewMutation::List &mutationsList, + uint64_t now) const { + for (auto &animation : inflightAnimations_) { + if (animation.surfaceId != surfaceId) { + continue; + } + + int incompleteAnimations = 0; + for (const auto &keyframe : animation.keyFrames) { + if (keyframe.type == AnimationConfigurationType::Noop) { + continue; + } + + auto const &baselineShadowView = keyframe.viewStart; + auto const &finalShadowView = keyframe.viewEnd; + + // The contract with the "keyframes generation" phase is that any animated + // node will have a valid configuration. + auto const layoutAnimationConfig = animation.layoutAnimationConfig; + auto const mutationConfig = + (keyframe.type == AnimationConfigurationType::Delete + ? layoutAnimationConfig.deleteConfig + : (keyframe.type == AnimationConfigurationType::Create + ? layoutAnimationConfig.createConfig + : layoutAnimationConfig.updateConfig)); + + // Interpolate + std::pair progress = + calculateAnimationProgress(now, animation, *mutationConfig); + double animationTimeProgressLinear = progress.first; + double animationInterpolationFactor = progress.second; + + auto mutatedShadowView = createInterpolatedShadowView( + animationInterpolationFactor, + *mutationConfig, + baselineShadowView, + finalShadowView); + + // Create the mutation instruction + mutationsList.push_back(ShadowViewMutation::UpdateMutation( + keyframe.parentView, baselineShadowView, mutatedShadowView, -1)); + + if (animationTimeProgressLinear < 1) { + incompleteAnimations++; + } + } + + // Are there no ongoing mutations left in this animation? + if (incompleteAnimations == 0) { + animation.completed = true; + } + } + + // Clear out finished animations + for (auto it = inflightAnimations_.begin(); + it != inflightAnimations_.end();) { + const auto &animation = *it; + if (animation.completed) { + // Queue up "final" mutations for all keyframes in the completed animation + for (auto const &keyframe : animation.keyFrames) { + if (keyframe.finalMutationForKeyFrame.hasValue()) { + mutationsList.push_back(*keyframe.finalMutationForKeyFrame); + } + } + + it = inflightAnimations_.erase(it); + } else { + it++; + } + } +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/animations/LayoutAnimationDriver.h b/ReactCommon/fabric/animations/LayoutAnimationDriver.h new file mode 100644 index 00000000000000..3aa04f99163bca --- /dev/null +++ b/ReactCommon/fabric/animations/LayoutAnimationDriver.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "LayoutAnimationKeyFrameManager.h" + +namespace facebook { +namespace react { + +class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager { + public: + LayoutAnimationDriver(LayoutAnimationStatusDelegate *delegate) + : LayoutAnimationKeyFrameManager(delegate) {} + + virtual ~LayoutAnimationDriver() {} + + protected: + virtual void animationMutationsForFrame( + SurfaceId surfaceId, + ShadowViewMutation::List &mutationsList, + uint64_t now) const override; + virtual double getProgressThroughAnimation( + AnimationKeyFrame const &keyFrame, + LayoutAnimation const *layoutAnimation, + ShadowView const &animationStateView) const override; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp new file mode 100644 index 00000000000000..91b73f5bdaea05 --- /dev/null +++ b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.cpp @@ -0,0 +1,944 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LayoutAnimationKeyFrameManager.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace facebook { +namespace react { + +static better::optional parseAnimationType(std::string param) { + if (param == "spring") { + return better::optional(AnimationType::Spring); + } + if (param == "linear") { + return better::optional(AnimationType::Linear); + } + if (param == "easeInEaseOut") { + return better::optional(AnimationType::EaseInEaseOut); + } + if (param == "easeIn") { + return better::optional(AnimationType::EaseIn); + } + if (param == "easeOut") { + return better::optional(AnimationType::EaseOut); + } + if (param == "keyboard") { + return better::optional(AnimationType::Keyboard); + } + + return {}; +} + +static better::optional parseAnimationProperty( + std::string param) { + if (param == "opacity") { + return better::optional(AnimationProperty::Opacity); + } + if (param == "scaleX") { + return better::optional(AnimationProperty::ScaleX); + } + if (param == "scaleY") { + return better::optional(AnimationProperty::ScaleY); + } + if (param == "scaleXY") { + return better::optional(AnimationProperty::ScaleXY); + } + + return {}; +} + +static better::optional parseAnimationConfig( + folly::dynamic const &config, + double defaultDuration, + bool parsePropertyType) { + if (config.empty() || !config.isObject()) { + return better::optional( + AnimationConfig{AnimationType::Linear, + AnimationProperty::NotApplicable, + defaultDuration, + 0, + 0, + 0}); + } + + auto const typeIt = config.find("type"); + if (typeIt == config.items().end()) { + return {}; + } + auto const animationTypeParam = typeIt->second; + if (animationTypeParam.empty() || !animationTypeParam.isString()) { + return {}; + } + const auto animationType = parseAnimationType(animationTypeParam.asString()); + if (!animationType) { + return {}; + } + + AnimationProperty animationProperty = AnimationProperty::NotApplicable; + if (parsePropertyType) { + auto const propertyIt = config.find("property"); + if (propertyIt == config.items().end()) { + return {}; + } + auto const animationPropertyParam = propertyIt->second; + if (animationPropertyParam.empty() || !animationPropertyParam.isString()) { + return {}; + } + const auto animationPropertyParsed = + parseAnimationProperty(animationPropertyParam.asString()); + if (!animationPropertyParsed) { + return {}; + } + animationProperty = *animationPropertyParsed; + } + + double duration = defaultDuration; + double delay = 0; + double springDamping = 0.5; + double initialVelocity = 0; + + auto const durationIt = config.find("duration"); + if (durationIt != config.items().end()) { + if (durationIt->second.isDouble()) { + duration = durationIt->second.asDouble(); + } else { + return {}; + } + } + + auto const delayIt = config.find("delay"); + if (delayIt != config.items().end()) { + if (delayIt->second.isDouble()) { + delay = delayIt->second.asDouble(); + } else { + return {}; + } + } + + auto const springDampingIt = config.find("springDamping"); + if (springDampingIt != config.items().end() && + springDampingIt->second.isDouble()) { + if (springDampingIt->second.isDouble()) { + springDamping = springDampingIt->second.asDouble(); + } else { + return {}; + } + } + + auto const initialVelocityIt = config.find("initialVelocity"); + if (initialVelocityIt != config.items().end()) { + if (initialVelocityIt->second.isDouble()) { + initialVelocity = initialVelocityIt->second.asDouble(); + } else { + return {}; + } + } + + return better::optional(AnimationConfig{*animationType, + animationProperty, + duration, + delay, + springDamping, + initialVelocity}); +} + +// Parse animation config from JS +static better::optional parseLayoutAnimationConfig( + folly::dynamic const &config) { + if (config.empty() || !config.isObject()) { + return {}; + } + + auto const durationIt = config.find("duration"); + if (durationIt == config.items().end() || !durationIt->second.isDouble()) { + return {}; + } + const double duration = durationIt->second.asDouble(); + + const auto createConfig = + parseAnimationConfig(config["create"], duration, true); + if (!createConfig) { + return {}; + } + + const auto updateConfig = + parseAnimationConfig(config["update"], duration, false); + if (!updateConfig) { + return {}; + } + + const auto deleteConfig = + parseAnimationConfig(config["delete"], duration, true); + if (!deleteConfig) { + return {}; + } + + return better::optional(LayoutAnimationConfig{ + duration, *createConfig, *updateConfig, *deleteConfig}); +} + +/** + * Globally configure next LayoutAnimation. + */ +void LayoutAnimationKeyFrameManager::uiManagerDidConfigureNextLayoutAnimation( + RawValue const &config, + std::shared_ptr successCallback, + std::shared_ptr errorCallback) const { + auto layoutAnimationConfig = + parseLayoutAnimationConfig((folly::dynamic)config); + + if (layoutAnimationConfig) { + std::lock_guard lock(currentAnimationMutex_); + currentAnimation_ = better::optional{ + LayoutAnimation{-1, + 0, + false, + *layoutAnimationConfig, + successCallback, + errorCallback, + {}}}; + } else { + // TODO: call errorCallback + LOG(ERROR) << "Parsing LayoutAnimationConfig failed: " + << (folly::dynamic)config; + } +} + +void LayoutAnimationKeyFrameManager::setLayoutAnimationStatusDelegate( + LayoutAnimationStatusDelegate *delegate) const { + std::lock_guard lock(layoutAnimationStatusDelegateMutex_); + layoutAnimationStatusDelegate_ = delegate; +} + +bool LayoutAnimationKeyFrameManager::shouldOverridePullTransaction() const { + return shouldAnimateFrame(); +} + +bool LayoutAnimationKeyFrameManager::shouldAnimateFrame() const { + // There is potentially a race here between getting and setting + // `currentMutation_`. We don't want to lock around this because then we're + // creating contention between pullTransaction and the JS thread. + return currentAnimation_ || !inflightAnimations_.empty(); +} + +static inline const float +interpolateFloats(float coefficient, float oldValue, float newValue) { + return oldValue + (newValue - oldValue) * coefficient; +} + +std::pair +LayoutAnimationKeyFrameManager::calculateAnimationProgress( + uint64_t now, + const LayoutAnimation &animation, + const AnimationConfig &mutationConfig) const { + uint64_t startTime = animation.startTime; + uint64_t delay = mutationConfig.delay; + uint64_t endTime = startTime + delay + mutationConfig.duration; + + static const float PI = 3.14159265358979323846; + + if (now >= endTime) { + return {1, 1}; + } + if (now < startTime + delay) { + return {0, 0}; + } + + double linearTimeProgression = 1 - + (double)(endTime - delay - now) / (double)(endTime - animation.startTime); + + if (mutationConfig.animationType == AnimationType::Linear) { + return {linearTimeProgression, linearTimeProgression}; + } else if (mutationConfig.animationType == AnimationType::EaseIn) { + // This is an accelerator-style interpolator. + // In the future, this parameter (2.0) could be adjusted. This has been the + // default for Classic RN forever. + return {linearTimeProgression, pow(linearTimeProgression, 2.0)}; + } else if (mutationConfig.animationType == AnimationType::EaseOut) { + // This is an decelerator-style interpolator. + // In the future, this parameter (2.0) could be adjusted. This has been the + // default for Classic RN forever. + return {linearTimeProgression, 1.0 - pow(1 - linearTimeProgression, 2.0)}; + } else if (mutationConfig.animationType == AnimationType::EaseInEaseOut) { + // This is a combination of accelerate+decelerate. + // The animation starts and ends slowly, and speeds up in the middle. + return {linearTimeProgression, + cos((linearTimeProgression + 1.0) * PI) / 2 + 0.5}; + } else if (mutationConfig.animationType == AnimationType::Spring) { + // Using mSpringDamping in this equation is not really the exact + // mathematical springDamping, but a good approximation We need to replace + // this equation with the right Factor that accounts for damping and + // friction + double damping = mutationConfig.springDamping; + return { + linearTimeProgression, + (1 + + pow(2, -10 * linearTimeProgression) * + sin((linearTimeProgression - damping / 4) * PI * 2 / damping))}; + } else { + return {linearTimeProgression, linearTimeProgression}; + } +} + +void LayoutAnimationKeyFrameManager::adjustDelayedMutationIndicesForMutation( + SurfaceId surfaceId, + ShadowViewMutation const &mutation) const { + bool isRemoveMutation = mutation.type == ShadowViewMutation::Type::Remove; + bool isInsertMutation = mutation.type == ShadowViewMutation::Type::Insert; + assert(isRemoveMutation || isInsertMutation); + + if (mutatedViewIsVirtual(mutation)) { + return; + } + + for (auto &inflightAnimation : inflightAnimations_) { + if (inflightAnimation.surfaceId != surfaceId) { + continue; + } + + for (auto it = inflightAnimation.keyFrames.begin(); + it != inflightAnimation.keyFrames.end(); + it++) { + auto &animatedKeyFrame = *it; + + // Detect if they're in the same view hierarchy, but not equivalent + // (We've already detected direct conflicts and handled them above) + if (animatedKeyFrame.parentView.tag != mutation.parentShadowView.tag) { + continue; + } + + if (animatedKeyFrame.type != AnimationConfigurationType::Noop) { + continue; + } + if (!animatedKeyFrame.finalMutationForKeyFrame.has_value()) { + continue; + } + ShadowViewMutation &finalAnimationMutation = + *animatedKeyFrame.finalMutationForKeyFrame; + + if (finalAnimationMutation.type != ShadowViewMutation::Type::Remove) { + continue; + } + + // Do we need to adjust the index of this operation? + if (isRemoveMutation && mutation.index <= finalAnimationMutation.index) { + finalAnimationMutation.index--; + } else if ( + isInsertMutation && mutation.index <= finalAnimationMutation.index) { + finalAnimationMutation.index++; + } + } + } +} + +better::optional +LayoutAnimationKeyFrameManager::pullTransaction( + SurfaceId surfaceId, + MountingTransaction::Number transactionNumber, + MountingTelemetry const &telemetry, + ShadowViewMutationList mutations) const { + // Current time in milliseconds + uint64_t now = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + + bool inflightAnimationsExistInitially = !inflightAnimations_.empty(); + + if (!mutations.empty()) { +#ifdef RN_SHADOW_TREE_INTROSPECTION + { + std::stringstream ss(getDebugDescription(mutations, {})); + std::string to; + while (std::getline(ss, to, '\n')) { + LOG(ERROR) + << "LayoutAnimationKeyFrameManager.cpp: got mutation list: Line: " + << to; + } + }; +#endif + + // What to do if we detect a conflict? Get current value and make + // that the baseline of the next animation. Scale the remaining time + // in the animation + // Types of conflicts and how we handle them: + // Update -> update: remove the previous update, make it the baseline of the + // next update (with current progress) Update -> remove: same, with final + // mutation being a remove Insert -> update: treat as update->update Insert + // -> remove: same, as update->remove Remove -> update/insert: not possible + // We just collect pairs here of and delete them + // from active animations. If another animation is queued up from the + // current mutations then these deleted mutations will serve as the baseline + // for the next animation. If not, the current mutations are executed + // immediately without issues. + std::vector< + std::tuple> + conflictingAnimations{}; + for (auto &mutation : mutations) { + auto const &baselineShadowView = + (mutation.type == ShadowViewMutation::Type::Insert) + ? mutation.newChildShadowView + : mutation.oldChildShadowView; + + for (auto &inflightAnimation : inflightAnimations_) { + if (inflightAnimation.surfaceId != surfaceId) { + continue; + } + + for (auto it = inflightAnimation.keyFrames.begin(); + it != inflightAnimation.keyFrames.end();) { + auto &animatedKeyFrame = *it; + + // Conflicting animation detected + if (animatedKeyFrame.tag == baselineShadowView.tag) { + auto const layoutAnimationConfig = + inflightAnimation.layoutAnimationConfig; + + auto const mutationConfig = + (animatedKeyFrame.type == AnimationConfigurationType::Delete + ? layoutAnimationConfig.deleteConfig + : (animatedKeyFrame.type == + AnimationConfigurationType::Create + ? layoutAnimationConfig.createConfig + : layoutAnimationConfig.updateConfig)); + + conflictingAnimations.push_back(std::make_tuple( + animatedKeyFrame, *mutationConfig, &inflightAnimation)); + + // Delete from existing animation + it = inflightAnimation.keyFrames.erase(it); + } else { + it++; + } + } + } + } + + // Are we animating this list of mutations? + better::optional currentAnimation{}; + { + std::lock_guard lock(currentAnimationMutex_); + if (currentAnimation_) { + currentAnimation = currentAnimation_; + currentAnimation_ = {}; + } + } + + if (currentAnimation) { + LayoutAnimation animation = currentAnimation.value(); + animation.surfaceId = surfaceId; + animation.startTime = now; + + // Pre-process list to: + // Catch remove+reinsert (reorders) + // Catch delete+create (reparenting) (this should be optimized away at + // the diffing level eventually?) + // TODO: to prevent this step we could tag Remove/Insert mutations as + // being moves on the Differ level, since we know that there? We could use + // TinyMap here, but it's not exposed by Differentiator (yet). + std::vector insertedTags; + std::vector createdTags; + std::unordered_map movedTags; + std::vector reparentedTags; + for (const auto &mutation : mutations) { + if (mutation.type == ShadowViewMutation::Type::Insert) { + insertedTags.push_back(mutation.newChildShadowView.tag); + } + if (mutation.type == ShadowViewMutation::Type::Create) { + createdTags.push_back(mutation.newChildShadowView.tag); + } + } + + // Process mutations list into operations that can be sent to platform + // immediately, and those that need to be animated Deletions, removals, + // updates are delayed and animated. Creations and insertions are sent to + // platform and then "animated in" with opacity updates. Upon completion, + // removals and deletions are sent to platform + ShadowViewMutation::List immediateMutations; + + // Remove operations that are actually moves should be copied to + // "immediate mutations". The corresponding "insert" will also be executed + // immediately and animated as an update. + std::vector keyFramesToAnimate; + std::vector movesToAnimate; + auto const layoutAnimationConfig = animation.layoutAnimationConfig; + for (auto &mutation : mutations) { + ShadowView baselineShadowView = + (mutation.type == ShadowViewMutation::Type::Delete || + mutation.type == ShadowViewMutation::Type::Remove + ? mutation.oldChildShadowView + : mutation.newChildShadowView); + auto const &componentDescriptor = + getComponentDescriptorForShadowView(baselineShadowView); + + auto mutationConfig = + (mutation.type == ShadowViewMutation::Type::Delete + ? layoutAnimationConfig.deleteConfig + : (mutation.type == ShadowViewMutation::Type::Insert + ? layoutAnimationConfig.createConfig + : layoutAnimationConfig.updateConfig)); + + bool isRemoveReinserted = + mutation.type == ShadowViewMutation::Type::Remove && + std::find( + insertedTags.begin(), + insertedTags.end(), + mutation.oldChildShadowView.tag) != insertedTags.end(); + + // Reparenting can result in a node being removed, inserted (moved) and + // also deleted and created in the same frame, with the same props etc. + // This should eventually be optimized out of the diffing algorithm, but + // for now we detect reparenting and prevent the corresponding + // Delete/Create instructions from being animated. + bool isReparented = + (mutation.type == ShadowViewMutation::Delete && + std::find( + createdTags.begin(), + createdTags.end(), + mutation.oldChildShadowView.tag) != createdTags.end()) || + (mutation.type == ShadowViewMutation::Create && + std::find( + reparentedTags.begin(), + reparentedTags.end(), + mutation.newChildShadowView.tag) != reparentedTags.end()); + + if (isRemoveReinserted) { + movedTags.insert({mutation.oldChildShadowView.tag, mutation}); + } + + if (isReparented && mutation.type == ShadowViewMutation::Delete) { + reparentedTags.push_back(mutation.oldChildShadowView.tag); + } + + // Inserts that follow a "remove" of the same tag should be treated as + // an update (move) animation. + bool wasInsertedTagRemoved = false; + bool haveConfiguration = mutationConfig.has_value(); + if (mutation.type == ShadowViewMutation::Type::Insert) { + // If this is a move, we actually don't want to copy this insert + // instruction to animated instructions - we want to + // generate an Update mutation for Remove+Insert pairs to animate + // the layout. + // The corresponding Remove and Insert instructions will instead + // be treated as "immediate" instructions. + auto movedIt = movedTags.find(mutation.newChildShadowView.tag); + wasInsertedTagRemoved = movedIt != movedTags.end(); + if (wasInsertedTagRemoved) { + mutationConfig = layoutAnimationConfig.updateConfig; + } + haveConfiguration = mutationConfig.has_value(); + + if (wasInsertedTagRemoved && haveConfiguration) { + movesToAnimate.push_back( + AnimationKeyFrame{{}, + AnimationConfigurationType::Update, + mutation.newChildShadowView.tag, + mutation.parentShadowView, + movedIt->second.oldChildShadowView, + mutation.newChildShadowView}); + } + } + + // Creates and inserts should also be executed immediately. + // Mutations that would otherwise be animated, but have no + // configuration, are also executed immediately. + if (isRemoveReinserted || !haveConfiguration || isReparented || + mutation.type == ShadowViewMutation::Type::Create || + mutation.type == ShadowViewMutation::Type::Insert) { + immediateMutations.push_back(mutation); + + // Adjust indices for any non-directly-conflicting animations that + // affect the same parent view by inserting or removing anything + // from the hierarchy. + if (mutation.type == ShadowViewMutation::Type::Insert || + mutation.type == ShadowViewMutation::Type::Remove) { + adjustDelayedMutationIndicesForMutation(surfaceId, mutation); + } + } + + // Deletes, non-move inserts, updates get animated + if (!wasInsertedTagRemoved && !isRemoveReinserted && !isReparented && + haveConfiguration && + mutation.type != ShadowViewMutation::Type::Create) { + ShadowView viewStart = ShadowView( + mutation.type == ShadowViewMutation::Type::Insert + ? mutation.newChildShadowView + : mutation.oldChildShadowView); + ShadowView viewFinal = ShadowView( + mutation.type == ShadowViewMutation::Type::Update + ? mutation.newChildShadowView + : viewStart); + ShadowView parent = mutation.parentShadowView; + Tag tag = viewStart.tag; + Tag parentTag = mutation.parentShadowView.tag; + + AnimationKeyFrame keyFrame{}; + if (mutation.type == ShadowViewMutation::Type::Insert) { + if (mutationConfig->animationProperty == + AnimationProperty::Opacity) { + auto props = componentDescriptor.cloneProps(viewStart.props, {}); + const auto viewProps = + dynamic_cast(props.get()); + if (viewProps != nullptr) { + const_cast(viewProps)->opacity = 0; + } + viewStart.props = props; + } + bool isScaleX = mutationConfig->animationProperty == + AnimationProperty::ScaleX || + mutationConfig->animationProperty == AnimationProperty::ScaleXY; + bool isScaleY = mutationConfig->animationProperty == + AnimationProperty::ScaleY || + mutationConfig->animationProperty == AnimationProperty::ScaleXY; + if (isScaleX || isScaleY) { + auto props = componentDescriptor.cloneProps(viewStart.props, {}); + const auto viewProps = + dynamic_cast(props.get()); + if (viewProps != nullptr) { + const_cast(viewProps)->transform = + Transform::Scale(isScaleX ? 0 : 1, isScaleY ? 0 : 1, 1); + } + viewStart.props = props; + } + + keyFrame = AnimationKeyFrame{{}, + AnimationConfigurationType::Create, + tag, + parent, + viewStart, + viewFinal, + 0}; + } else if (mutation.type == ShadowViewMutation::Type::Delete) { + if (mutationConfig->animationProperty == + AnimationProperty::Opacity) { + auto props = componentDescriptor.cloneProps(viewFinal.props, {}); + const auto viewProps = + dynamic_cast(props.get()); + if (viewProps != nullptr) { + const_cast(viewProps)->opacity = 0; + } + viewFinal.props = props; + } + bool isScaleX = mutationConfig->animationProperty == + AnimationProperty::ScaleX || + mutationConfig->animationProperty == AnimationProperty::ScaleXY; + bool isScaleY = mutationConfig->animationProperty == + AnimationProperty::ScaleY || + mutationConfig->animationProperty == AnimationProperty::ScaleXY; + if (isScaleX || isScaleY) { + auto props = componentDescriptor.cloneProps(viewFinal.props, {}); + const auto viewProps = + dynamic_cast(props.get()); + if (viewProps != nullptr) { + const_cast(viewProps)->transform = + Transform::Scale(isScaleX ? 0 : 1, isScaleY ? 0 : 1, 1); + } + viewFinal.props = props; + } + + keyFrame = AnimationKeyFrame{ + better::optional(mutation), + AnimationConfigurationType::Delete, + tag, + parent, + viewStart, + viewFinal, + 0}; + } else if (mutation.type == ShadowViewMutation::Type::Update) { + viewFinal = ShadowView(mutation.newChildShadowView); + + keyFrame = AnimationKeyFrame{ + better::optional(mutation), + AnimationConfigurationType::Update, + tag, + parent, + viewStart, + viewFinal, + 0}; + } else { + // This should just be "Remove" instructions that are not animated + // (either this is a "move", or there's a corresponding "Delete" + // that is animated). We configure it as a Noop animation so it is + // executed when all the other animations are completed. + assert(mutation.type == ShadowViewMutation::Type::Remove); + + // For remove instructions: since the execution of the Remove + // instruction will be delayed and therefore may execute outside of + // otherwise-expected order, other views may be inserted before the + // Remove is executed, requiring index adjustment. + { + int adjustedIndex = mutation.index; + for (const auto &otherMutation : mutations) { + if (otherMutation.type == ShadowViewMutation::Type::Insert && + otherMutation.parentShadowView.tag == parentTag) { + if (otherMutation.index <= adjustedIndex && + !mutatedViewIsVirtual(otherMutation)) { + adjustedIndex++; + } + } + } + + mutation = ShadowViewMutation::RemoveMutation( + mutation.parentShadowView, + mutation.oldChildShadowView, + adjustedIndex); + } + + keyFrame = AnimationKeyFrame{ + better::optional(mutation), + AnimationConfigurationType::Noop, + tag, + parent, + {}, + {}, + 0}; + } + + // Handle conflicting animations + for (auto &conflictingKeyframeTuple : conflictingAnimations) { + auto &conflictingKeyFrame = std::get<0>(conflictingKeyframeTuple); + auto const &conflictingMutationBaselineShadowView = + conflictingKeyFrame.viewStart; + + // We've found a conflict. + if (conflictingMutationBaselineShadowView.tag == tag) { + // What's the progress of this ongoing animation? + double conflictingAnimationProgress = + calculateAnimationProgress( + now, + *std::get<2>(conflictingKeyframeTuple), + std::get<1>(conflictingKeyframeTuple)) + .first; + + // Get a baseline ShadowView at the current progress of the + // inflight animation. TODO: handle multiple properties being + // animated separately? + auto interpolatedInflightShadowView = + createInterpolatedShadowView( + conflictingAnimationProgress, + std::get<1>(conflictingKeyframeTuple), + conflictingKeyFrame.viewStart, + conflictingKeyFrame.viewEnd); + + // Pick a Prop or layout property, depending on the current + // animation configuration. Figure out how much progress we've + // already made in the current animation, and start the animation + // from this point. + keyFrame.viewStart = interpolatedInflightShadowView; + keyFrame.initialProgress = getProgressThroughAnimation( + keyFrame, &animation, interpolatedInflightShadowView); + + // We're guaranteed that a tag only has one animation associated + // with it, so we can break here. If we support multiple + // animations and animation curves over the same tag in the + // future, this will need to be modified to support that. + break; + } + } + + keyFramesToAnimate.push_back(keyFrame); + } + } + +#ifdef RN_SHADOW_TREE_INTROSPECTION + { + std::stringstream ss(getDebugDescription(immediateMutations, {})); + std::string to; + while (std::getline(ss, to, '\n')) { + LOG(ERROR) + << "LayoutAnimationKeyFrameManager.cpp: got IMMEDIATE list: Line: " + << to; + } + } + + { + std::stringstream ss(getDebugDescription(mutationsToAnimate, {})); + std::string to; + while (std::getline(ss, to, '\n')) { + LOG(ERROR) + << "LayoutAnimationKeyFrameManager.cpp: got FINAL list: Line: " + << to; + } + } +#endif + + animation.keyFrames = keyFramesToAnimate; + inflightAnimations_.push_back(animation); + + // These will be executed immediately. + mutations = immediateMutations; + } /* if (currentAnimation) */ else { + // If there's no "next" animation, make sure we queue up "final" + // operations from all ongoing animations. + ShadowViewMutationList finalMutationsForConflictingAnimations{}; + for (auto &conflictingKeyframeTuple : conflictingAnimations) { + auto &keyFrame = std::get<0>(conflictingKeyframeTuple); + if (keyFrame.finalMutationForKeyFrame.hasValue()) { + finalMutationsForConflictingAnimations.push_back( + *keyFrame.finalMutationForKeyFrame); + } + } + + // Append mutations to this list and swap - so that the final + // conflicting mutations happen before any other mutations + finalMutationsForConflictingAnimations.insert( + finalMutationsForConflictingAnimations.end(), + mutations.begin(), + mutations.end()); + mutations = finalMutationsForConflictingAnimations; + + // Adjust pending mutation indices base on these operations + for (auto &mutation : mutations) { + if (mutation.type == ShadowViewMutation::Type::Insert || + mutation.type == ShadowViewMutation::Type::Remove) { + adjustDelayedMutationIndicesForMutation(surfaceId, mutation); + } + } + } + } // if (mutations) + + // We never commit a different root or modify anything - + // we just send additional mutations to the mounting layer until the + // animations are finished and the mounting layer (view) represents exactly + // what is in the most recent shadow tree + // Add animation mutations to the end of our existing mutations list in this + // function. + ShadowViewMutationList mutationsForAnimation{}; + animationMutationsForFrame(surfaceId, mutationsForAnimation, now); + + // Adjust pending mutation indices base on these operations + // For example: if a final "remove" mutation has been performed, and there is + // another that has not yet been executed because it is a part of an ongoing + // animation, its index may need to be adjusted. + for (auto const &animatedMutation : mutationsForAnimation) { + if (animatedMutation.type == ShadowViewMutation::Type::Insert || + animatedMutation.type == ShadowViewMutation::Type::Remove) { + adjustDelayedMutationIndicesForMutation(surfaceId, animatedMutation); + } + } + + mutations.insert( + mutations.end(), + mutationsForAnimation.begin(), + mutationsForAnimation.end()); + + // Signal to delegate if all animations are complete, or if we were not + // animating anything and now some animation exists. + if (inflightAnimationsExistInitially && inflightAnimations_.empty()) { + std::lock_guard lock(layoutAnimationStatusDelegateMutex_); + if (layoutAnimationStatusDelegate_ != nullptr) { + layoutAnimationStatusDelegate_->onAllAnimationsComplete(); + } + } else if ( + !inflightAnimationsExistInitially && !inflightAnimations_.empty()) { + std::lock_guard lock(layoutAnimationStatusDelegateMutex_); + if (layoutAnimationStatusDelegate_ != nullptr) { + layoutAnimationStatusDelegate_->onAnimationStarted(); + } + } + + // TODO: fill in telemetry + return MountingTransaction{ + surfaceId, transactionNumber, std::move(mutations), {}}; +} + +bool LayoutAnimationKeyFrameManager::mutatedViewIsVirtual( + ShadowViewMutation const &mutation) const { + bool viewIsVirtual = false; + + // TODO: extract this into an Android platform-specific class + // Explanation: for "Insert" mutations, oldChildShadowView is always empty. + // for "Remove" mutations, newChildShadowView is always empty. +#ifdef ANDROID + viewIsVirtual = + mutation.newChildShadowView.layoutMetrics == EmptyLayoutMetrics && + mutation.oldChildShadowView.layoutMetrics == EmptyLayoutMetrics; +#endif + + return viewIsVirtual; +} + +ComponentDescriptor const & +LayoutAnimationKeyFrameManager::getComponentDescriptorForShadowView( + ShadowView const &shadowView) const { + return componentDescriptorRegistry_->at(shadowView.componentHandle); +} + +void LayoutAnimationKeyFrameManager::setComponentDescriptorRegistry( + const SharedComponentDescriptorRegistry &componentDescriptorRegistry) { + componentDescriptorRegistry_ = componentDescriptorRegistry; +} + +/** + * Given a `progress` between 0 and 1, a mutation and LayoutAnimation config, + * return a ShadowView with mutated props and/or LayoutMetrics. + * + * @param progress + * @param layoutAnimation + * @param animatedMutation + * @return + */ +ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView( + double progress, + AnimationConfig const &animationConfig, + ShadowView startingView, + ShadowView finalView) const { + ComponentDescriptor const &componentDescriptor = + getComponentDescriptorForShadowView(startingView); + auto mutatedShadowView = ShadowView(startingView); + + // Animate opacity or scale/transform + mutatedShadowView.props = componentDescriptor.interpolateProps( + progress, startingView.props, finalView.props); + + // Interpolate LayoutMetrics + LayoutMetrics const &finalLayoutMetrics = finalView.layoutMetrics; + LayoutMetrics const &baselineLayoutMetrics = startingView.layoutMetrics; + LayoutMetrics interpolatedLayoutMetrics = finalLayoutMetrics; + interpolatedLayoutMetrics.frame.origin.x = interpolateFloats( + progress, + baselineLayoutMetrics.frame.origin.x, + finalLayoutMetrics.frame.origin.x); + interpolatedLayoutMetrics.frame.origin.y = interpolateFloats( + progress, + baselineLayoutMetrics.frame.origin.y, + finalLayoutMetrics.frame.origin.y); + interpolatedLayoutMetrics.frame.size.width = interpolateFloats( + progress, + baselineLayoutMetrics.frame.size.width, + finalLayoutMetrics.frame.size.width); + interpolatedLayoutMetrics.frame.size.height = interpolateFloats( + progress, + baselineLayoutMetrics.frame.size.height, + finalLayoutMetrics.frame.size.height); + mutatedShadowView.layoutMetrics = interpolatedLayoutMetrics; + + return mutatedShadowView; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h new file mode 100644 index 00000000000000..444e310c454675 --- /dev/null +++ b/ReactCommon/fabric/animations/LayoutAnimationKeyFrameManager.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +// This corresponds exactly with JS. +enum class AnimationType { + Spring, + Linear, + EaseInEaseOut, + EaseIn, + EaseOut, + Keyboard +}; +enum class AnimationProperty { + NotApplicable, + Opacity, + ScaleX, + ScaleY, + ScaleXY +}; +enum class AnimationConfigurationType { + Noop, // for animation placeholders that are not animated, and should be + // executed once other animations have completed + Create, + Update, + Delete +}; + +// This corresponds exactly with JS. +struct AnimationConfig { + AnimationType animationType; + AnimationProperty animationProperty; + double duration; // these are perhaps better represented as uint64_t, but they + // come from JS as doubles + double delay; + double springDamping; + double initialVelocity; +}; + +// This corresponds exactly with JS. +struct LayoutAnimationConfig { + double duration; // ms + better::optional createConfig; + better::optional updateConfig; + better::optional deleteConfig; +}; + +struct AnimationKeyFrame { + // The mutation that should be executed once the animation completes + // (optional). + better::optional finalMutationForKeyFrame; + + // The type of animation this is (for configuration purposes) + AnimationConfigurationType type; + + // Tag representing the node being animated. + Tag tag; + + ShadowView parentView; + + // ShadowView representing the start and end points of this animation. + ShadowView viewStart; + ShadowView viewEnd; + + // If an animation interrupts an existing one, the starting state may actually + // be halfway through the intended transition. + double initialProgress; +}; + +struct LayoutAnimation { + SurfaceId surfaceId; + uint64_t startTime; + bool completed = false; + LayoutAnimationConfig layoutAnimationConfig; + std::shared_ptr successCallback; + std::shared_ptr errorCallback; + std::vector keyFrames; +}; + +class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, + public MountingOverrideDelegate { + public: + LayoutAnimationKeyFrameManager(LayoutAnimationStatusDelegate *delegate) + : layoutAnimationStatusDelegate_(delegate) { + // This is the ONLY place where we set or access + // layoutAnimationStatusDelegate_ without a mutex. + } + ~LayoutAnimationKeyFrameManager() {} + + void uiManagerDidConfigureNextLayoutAnimation( + RawValue const &config, + std::shared_ptr successCallback, + std::shared_ptr errorCallback) const override; + void setComponentDescriptorRegistry(SharedComponentDescriptorRegistry const & + componentDescriptorRegistry) override; + + // TODO: add SurfaceId to this API as well + bool shouldAnimateFrame() const override; + + bool shouldOverridePullTransaction() const override; + + // This is used to "hijack" the diffing process to figure out which mutations + // should be animated. The mutations returned by this function will be + // executed immediately. + better::optional pullTransaction( + SurfaceId surfaceId, + MountingTransaction::Number number, + MountingTelemetry const &telemetry, + ShadowViewMutationList mutations) const override; + + // LayoutAnimationStatusDelegate - this is for the platform to get + // signal when animations start and complete. Setting and resetting this + // delegate is protected by a mutex; ALL method calls into this delegate are + // also protected by the mutex! The only way to set this without a mutex is + // via a constructor. + public: + void setLayoutAnimationStatusDelegate( + LayoutAnimationStatusDelegate *delegate) const; + + private: + mutable std::mutex layoutAnimationStatusDelegateMutex_; + mutable LayoutAnimationStatusDelegate *layoutAnimationStatusDelegate_{}; + + void adjustDelayedMutationIndicesForMutation( + SurfaceId surfaceId, + ShadowViewMutation const &mutation) const; + + protected: + bool mutatedViewIsVirtual(ShadowViewMutation const &mutation) const; + + ComponentDescriptor const &getComponentDescriptorForShadowView( + ShadowView const &shadowView) const; + std::pair calculateAnimationProgress( + uint64_t now, + LayoutAnimation const &animation, + AnimationConfig const &mutationConfig) const; + + ShadowView createInterpolatedShadowView( + double progress, + AnimationConfig const &animationConfig, + ShadowView startingView, + ShadowView finalView) const; + + virtual void animationMutationsForFrame( + SurfaceId surfaceId, + ShadowViewMutation::List &mutationsList, + uint64_t now) const = 0; + + virtual double getProgressThroughAnimation( + AnimationKeyFrame const &keyFrame, + LayoutAnimation const *layoutAnimation, + ShadowView const &animationStateView) const = 0; + + SharedComponentDescriptorRegistry componentDescriptorRegistry_; + mutable better::optional currentAnimation_{}; + mutable std::mutex currentAnimationMutex_; + + /** + * All mutations of inflightAnimations_ are thread-safe as long as + * we keep the contract of: only mutate it within the context of + * `pullTransaction`. If that contract is held, this is implicitly protected + * by the MountingCoordinator's mutex. + */ + mutable std::vector inflightAnimations_{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/attributedstring/BUCK b/ReactCommon/fabric/attributedstring/BUCK index cb6d8c234116b8..f65fe7e2611b01 100644 --- a/ReactCommon/fabric/attributedstring/BUCK +++ b/ReactCommon/fabric/attributedstring/BUCK @@ -37,11 +37,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/attributedstring/conversions.h b/ReactCommon/fabric/attributedstring/conversions.h index 774913dcd326e9..3046e8f79830d3 100644 --- a/ReactCommon/fabric/attributedstring/conversions.h +++ b/ReactCommon/fabric/attributedstring/conversions.h @@ -611,9 +611,9 @@ inline folly::dynamic toDynamic(const AttributedString &attributedString) { if (fragment.isAttachment()) { dynamicFragment["isAttachment"] = true; dynamicFragment["width"] = - (int)fragment.parentShadowView.layoutMetrics.frame.size.width; + fragment.parentShadowView.layoutMetrics.frame.size.width; dynamicFragment["height"] = - (int)fragment.parentShadowView.layoutMetrics.frame.size.height; + fragment.parentShadowView.layoutMetrics.frame.size.height; } dynamicFragment["textAttributes"] = toDynamic(fragment.textAttributes); fragments.push_back(dynamicFragment); diff --git a/ReactCommon/fabric/componentregistry/BUCK b/ReactCommon/fabric/componentregistry/BUCK index 1eec505f7e2cdf..d993cb0e59d7c9 100644 --- a/ReactCommon/fabric/componentregistry/BUCK +++ b/ReactCommon/fabric/componentregistry/BUCK @@ -36,11 +36,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/activityindicator/BUCK b/ReactCommon/fabric/components/activityindicator/BUCK index b0eaa75eae36a7..47d57870130a0c 100644 --- a/ReactCommon/fabric/components/activityindicator/BUCK +++ b/ReactCommon/fabric/components/activityindicator/BUCK @@ -35,10 +35,9 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/art/ARTBaseShadowNode.h b/ReactCommon/fabric/components/art/ARTBaseShadowNode.h new file mode 100644 index 00000000000000..e674551dc23d4e --- /dev/null +++ b/ReactCommon/fabric/components/art/ARTBaseShadowNode.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class ARTBaseShadowNode { + public: + int test; + virtual ARTElement::Shared getARTElement() const = 0; + + static void buildElements( + ShadowNode const &parentNode, + ARTElement::ListOfShared &outNodes) { + for (auto const &child : parentNode.getChildren()) { + auto baseShadowNode = + std::dynamic_pointer_cast(child); + if (baseShadowNode) { + outNodes.push_back(baseShadowNode->getARTElement()); + } + } + } +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/BUCK b/ReactCommon/fabric/components/art/BUCK new file mode 100644 index 00000000000000..50ba2970986320 --- /dev/null +++ b/ReactCommon/fabric/components/art/BUCK @@ -0,0 +1,94 @@ +load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode") +load( + "//tools/build_defs/oss:rn_defs.bzl", + "ANDROID", + "APPLE", + "CXX", + "YOGA_CXX_TARGET", + "fb_xplat_cxx_test", + "get_apple_compiler_flags", + "get_apple_inspector_flags", + "react_native_xplat_target", + "rn_xplat_cxx_library", + "subdir_glob", +) + +APPLE_COMPILER_FLAGS = get_apple_compiler_flags() + +rn_xplat_cxx_library( + name = "art", + srcs = glob( + ["**/*.cpp"], + exclude = glob(["tests/**/*.cpp"]), + ), + headers = glob( + ["**/*.h"], + exclude = glob(["tests/**/*.h"]), + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "*.h"), + ("group", "*.h"), + ("shape", "*.h"), + ("state", "*.h"), + ("surfaceview", "*.h"), + ("text", "*.h"), + ], + prefix = "react/components/art", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + cxx_tests = [":tests"], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + force_static = True, + labels = ["supermodule:xplat/default/public.react_native.playground"], + platforms = (ANDROID, APPLE, CXX), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + visibility = ["PUBLIC"], + deps = [ + "//third-party/glog:glog", + "//xplat/fbsystrace:fbsystrace", + "//xplat/folly:container_evicting_cache_map", + "//xplat/folly:headers_only", + "//xplat/folly:memory", + "//xplat/folly:molly", + YOGA_CXX_TARGET, + react_native_xplat_target("utils:utils"), + react_native_xplat_target("fabric/core:core"), + react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("fabric/graphics:graphics"), + react_native_xplat_target("fabric/uimanager:uimanager"), + ], +) + +fb_xplat_cxx_test( + name = "tests", + srcs = glob(["tests/**/*.cpp"]), + headers = glob(["tests/**/*.h"]), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + platforms = ( + # ANDROID, + # APPLE, + CXX + ), + deps = [ + ":art", + "//xplat/folly:molly", + "//xplat/third-party/gmock:gtest", + ], +) diff --git a/ReactCommon/fabric/components/art/conversions.h b/ReactCommon/fabric/components/art/conversions.h new file mode 100644 index 00000000000000..b234c6d0dbdfe8 --- /dev/null +++ b/ReactCommon/fabric/components/art/conversions.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +#ifdef ANDROID + +inline folly::dynamic toDynamic(ARTGroup const &group); +inline folly::dynamic toDynamic(ARTShape const &shape); +inline folly::dynamic toDynamic(ARTText const &text); +inline folly::dynamic toDynamic(ARTElement const &element); + +inline folly::dynamic toDynamic(std::vector const &elements) { + folly::dynamic result = folly::dynamic::array(); + for (auto const &element : elements) { + result.push_back(element); + } + return result; +} + +inline void addOptionalKey( + folly::dynamic &map, + std::string const &key, + std::vector const &values) { + if (values.size() > 0) { + map[key] = toDynamic(values); + } +} + +inline folly::dynamic toDynamic(ARTElement::ListOfShared const &elements) { + folly::dynamic children = folly::dynamic::array(); + for (auto const &element : elements) { + children.push_back(element->getDynamic()); + } + return children; +} + +inline folly::dynamic toDynamic(ARTGroup const &group) { + folly::dynamic result = folly::dynamic::object(); + result["opacity"] = group.opacity; + result["type"] = 1; + if (group.elements.size() > 0) { + result["elements"] = toDynamic(group.elements); + } + addOptionalKey(result, "clipping", group.clipping); + result["transform"] = toDynamic(group.transform); + return result; +} + +inline folly::dynamic toDynamic(ARTShape const &shape) { + folly::dynamic result = folly::dynamic::object(); + result["type"] = 2; + result["opacity"] = shape.opacity; + result["transform"] = toDynamic(shape.transform); + addOptionalKey(result, "d", shape.d); + addOptionalKey(result, "stroke", shape.stroke); + addOptionalKey(result, "strokeDash", shape.strokeDash); + addOptionalKey(result, "fill", shape.fill); + result["strokeWidth"] = shape.strokeWidth; + result["strokeCap"] = shape.strokeCap; + result["strokeJoin"] = shape.strokeJoin; + return result; +} + +inline folly::dynamic toDynamic(ARTTextAlignment const &aligment) { + switch (aligment) { + case ARTTextAlignment::Right: + return 1; + break; + case ARTTextAlignment::Center: + return 2; + break; + case ARTTextAlignment::Default: + default: + return 0; + break; + } +} + +inline folly::dynamic toDynamic(ARTTextFrame const &frame) { + folly::dynamic result = folly::dynamic::object(); + folly::dynamic font = folly::dynamic::object(); + font["fontSize"] = frame.font.fontSize; + font["fontStyle"] = frame.font.fontStyle; + font["fontFamily"] = frame.font.fontFamily; + font["fontWeight"] = frame.font.fontWeight; + result["font"] = font; + auto lines = frame.lines; + if (lines.size() > 0) { + folly::dynamic serializedLines = folly::dynamic::array(); + for (int i = 0; i < lines.size(); i++) { + serializedLines.push_back(lines[i]); + } + result["lines"] = serializedLines; + } + return result; +} + +inline folly::dynamic toDynamic(ARTText const &text) { + folly::dynamic result = folly::dynamic::object(); + result["type"] = 3; + result["opacity"] = text.opacity; + result["transform"] = toDynamic(text.transform); + addOptionalKey(result, "d", text.d); + addOptionalKey(result, "stroke", text.stroke); + addOptionalKey(result, "strokeDash", text.strokeDash); + addOptionalKey(result, "fill", text.fill); + result["strokeWidth"] = text.strokeWidth; + result["strokeCap"] = text.strokeCap; + result["strokeJoin"] = text.strokeJoin; + result["alignment"] = toDynamic(text.alignment); + result["frame"] = toDynamic(text.frame); + return result; +} + +inline folly::dynamic toDynamic(ARTSurfaceViewState const &surfaceViewState) { + folly::dynamic result = folly::dynamic::object(); + result["elements"] = toDynamic(surfaceViewState.elements); + return result; +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupComponentDescriptor.h b/ReactCommon/fabric/components/art/group/ARTGroupComponentDescriptor.h new file mode 100644 index 00000000000000..d2c3bd9424ce03 --- /dev/null +++ b/ReactCommon/fabric/components/art/group/ARTGroupComponentDescriptor.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using ARTGroupComponentDescriptor = + ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupProps.cpp b/ReactCommon/fabric/components/art/group/ARTGroupProps.cpp new file mode 100644 index 00000000000000..04e9c60237656d --- /dev/null +++ b/ReactCommon/fabric/components/art/group/ARTGroupProps.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +namespace facebook { +namespace react { + +ARTGroupProps::ARTGroupProps( + const ARTGroupProps &sourceProps, + const RawProps &rawProps) + : Props(sourceProps, rawProps), + opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, {1.0})), + transform( + convertRawProp(rawProps, "transform", sourceProps.transform, {})), + clipping( + convertRawProp(rawProps, "clipping", sourceProps.clipping, {})){}; + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE +SharedDebugStringConvertibleList RawTextProps::getDebugProps() const { + return {debugStringConvertibleItem("opacity", opacity)}; +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupProps.h b/ReactCommon/fabric/components/art/group/ARTGroupProps.h new file mode 100644 index 00000000000000..2827288613fe38 --- /dev/null +++ b/ReactCommon/fabric/components/art/group/ARTGroupProps.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class ARTGroupProps; + +class ARTGroupProps : public Props { + public: + ARTGroupProps() = default; + ARTGroupProps(const ARTGroupProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + Float opacity{1.0}; + std::vector transform{}; + std::vector clipping{}; + +#pragma mark - DebugStringConvertible +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.cpp b/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.cpp new file mode 100644 index 00000000000000..7d5301a53c43b2 --- /dev/null +++ b/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char ARTGroupComponentName[] = "ARTGroup"; + +ARTElement::Shared ARTGroupShadowNode::getARTElement() const { + auto elements = ARTElement::ListOfShared{}; + for (auto const &child : getChildren()) { + auto node = std::dynamic_pointer_cast(child); + if (node) { + elements.push_back(node->getARTElement()); + } + } + + auto props = getConcreteProps(); + return std::make_shared( + props.opacity, props.transform, elements, props.clipping); +} +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.h b/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.h new file mode 100644 index 00000000000000..dd85dffe66eaae --- /dev/null +++ b/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char ARTGroupComponentName[]; + +/* + * `ShadowNode` for component. + */ +class ARTGroupShadowNode : public ConcreteShadowNode< + ARTGroupComponentName, + ShadowNode, + ARTGroupProps>, + public ARTBaseShadowNode { + public: + using ConcreteShadowNode::ConcreteShadowNode; + + virtual ARTElement::Shared getARTElement() const override; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeComponentDescriptor.h b/ReactCommon/fabric/components/art/shape/ARTShapeComponentDescriptor.h new file mode 100644 index 00000000000000..5d65696f7dbaf2 --- /dev/null +++ b/ReactCommon/fabric/components/art/shape/ARTShapeComponentDescriptor.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using ARTShapeComponentDescriptor = + ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeProps.cpp b/ReactCommon/fabric/components/art/shape/ARTShapeProps.cpp new file mode 100644 index 00000000000000..9b1c79c00aedce --- /dev/null +++ b/ReactCommon/fabric/components/art/shape/ARTShapeProps.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +namespace facebook { +namespace react { + +ARTShapeProps::ARTShapeProps( + const ARTShapeProps &sourceProps, + const RawProps &rawProps) + : Props(sourceProps, rawProps), + + opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, {1.0})), + transform( + convertRawProp(rawProps, "transform", sourceProps.transform, {})), + d(convertRawProp(rawProps, "d", sourceProps.d, {})), + stroke(convertRawProp(rawProps, "stroke", sourceProps.stroke, {})), + strokeDash( + convertRawProp(rawProps, "strokeDash", sourceProps.strokeDash, {})), + fill(convertRawProp(rawProps, "fill", sourceProps.fill, {})), + strokeWidth(convertRawProp( + rawProps, + "strokeWidth", + sourceProps.strokeWidth, + {1.0})), + strokeCap( + convertRawProp(rawProps, "strokeCap", sourceProps.strokeCap, {1})), + strokeJoin( + convertRawProp(rawProps, "strokeJoin", sourceProps.strokeJoin, {1})){ + + }; + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE +SharedDebugStringConvertibleList RawTextProps::getDebugProps() const { + return {debugStringConvertibleItem("opacity", opacity)}; +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeProps.h b/ReactCommon/fabric/components/art/shape/ARTShapeProps.h new file mode 100644 index 00000000000000..7323f7ccd07b25 --- /dev/null +++ b/ReactCommon/fabric/components/art/shape/ARTShapeProps.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class ARTShapeProps; + +class ARTShapeProps : public Props { + public: + ARTShapeProps() = default; + ARTShapeProps(const ARTShapeProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + Float opacity{1.0}; + std::vector transform{}; + std::vector d{}; + std::vector stroke{}; + std::vector strokeDash{}; + std::vector fill{}; + Float strokeWidth{1.0}; + int strokeCap{1}; + int strokeJoin{1}; + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE + SharedDebugStringConvertibleList getDebugProps() const override; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.cpp b/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.cpp new file mode 100644 index 00000000000000..cb11199fd06576 --- /dev/null +++ b/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "ARTShapeShadowNode.h" +#include +#include +namespace facebook { +namespace react { + +extern const char ARTShapeComponentName[] = "ARTShape"; + +ARTElement::Shared ARTShapeShadowNode::getARTElement() const { + auto props = getConcreteProps(); + return std::make_shared( + props.opacity, + props.transform, + props.d, + props.stroke, + props.strokeDash, + props.fill, + props.strokeWidth, + props.strokeCap, + props.strokeJoin); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.h b/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.h new file mode 100644 index 00000000000000..0373aef45cf34c --- /dev/null +++ b/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char ARTShapeComponentName[]; + +/* + * `ShadowNode` for component. + */ +class ARTShapeShadowNode : public ConcreteShadowNode< + ARTShapeComponentName, + ShadowNode, + ARTShapeProps>, + public ARTBaseShadowNode { + public: + using ConcreteShadowNode::ConcreteShadowNode; + + virtual ARTElement::Shared getARTElement() const override; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerMode.java b/ReactCommon/fabric/components/art/state/ARTElement.cpp similarity index 59% rename from ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerMode.java rename to ReactCommon/fabric/components/art/state/ARTElement.cpp index d6c28ebd348db8..5b1ed74cb61c3a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerMode.java +++ b/ReactCommon/fabric/components/art/state/ARTElement.cpp @@ -5,10 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -package com.facebook.react.modules.timepicker; +#include -public enum TimePickerMode { - CLOCK, - SPINNER, - DEFAULT -} +namespace facebook { +namespace react {} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTElement.h b/ReactCommon/fabric/components/art/state/ARTElement.h new file mode 100644 index 00000000000000..1b3b82b1685a9e --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTElement.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +/* + * Simple, cross-platfrom, React-specific implementation of base ART Element + */ +class ARTElement { + public: + using Shared = std::shared_ptr; + using ListOfShared = better::small_vector; + + ARTElement() = default; + ARTElement( + ARTElementType elementType, + Float opacity, + std::vector transform) + : elementType(elementType), opacity(opacity), transform(transform){}; + virtual ~ARTElement(){}; + + ARTElementType elementType; + Float opacity; + std::vector transform; + + virtual bool operator==(const ARTElement &rhs) const = 0; + virtual bool operator!=(const ARTElement &rhs) const = 0; + friend bool operator==(ListOfShared e1, ListOfShared e2) { + bool equals = e1.size() == e2.size(); + for (int i = 0; i < equals && e1.size(); i++) { + // Pointer equality - this will work if both are pointing at the same + // object, or both are nullptr + if (e1[i] == e2[i]) { + continue; + } + + // Get pointers from both + // If one is null, we know they can't both be null because of the above + // check + auto ptr1 = e1[i].get(); + auto ptr2 = e2[i].get(); + if (ptr1 == nullptr || ptr2 == nullptr) { + equals = false; + break; + } + + // Dereference and compare objects + if (*ptr1 != *ptr2) { + equals = false; + break; + } + } + + return equals; + }; + +#ifdef ANDROID + virtual folly::dynamic getDynamic() const = 0; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTGroup.cpp b/ReactCommon/fabric/components/art/state/ARTGroup.cpp new file mode 100644 index 00000000000000..f3a8c6ee59b5c6 --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTGroup.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +namespace facebook { +namespace react { + +bool ARTGroup::operator==(const ARTElement &rhs) const { + if (rhs.elementType != ARTElementType::Group) { + return false; + } + auto group = (const ARTGroup &)(rhs); + return std::tie(elementType, opacity, transform, clipping) == + std::tie( + group.elementType, + group.opacity, + group.transform, + group.clipping) && + elements == group.elements; +} + +bool ARTGroup::operator!=(const ARTElement &rhs) const { + return !(*this == rhs); +} + +#ifdef ANDROID +folly::dynamic ARTGroup::getDynamic() const { + return toDynamic(*this); +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTGroup.h b/ReactCommon/fabric/components/art/state/ARTGroup.h new file mode 100644 index 00000000000000..fa86e7923655c0 --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTGroup.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +/* + * Simple, cross-platfrom, React-specific implementation of ART Group element + */ +class ARTGroup : public ARTElement { + public: + using Shared = std::shared_ptr; + ARTGroup( + Float opacity, + std::vector transform, + ARTGroup::ListOfShared elements, + std::vector clipping) + : ARTElement(ARTElementType::Group, opacity, transform), + elements(elements), + clipping(clipping){}; + ARTGroup() = default; + virtual ~ARTGroup(){}; + + ARTElement::ListOfShared elements{}; + + std::vector clipping{}; + + bool operator==(const ARTElement &rhs) const override; + bool operator!=(const ARTElement &rhs) const override; + +#ifdef ANDROID + folly::dynamic getDynamic() const override; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTShape.cpp b/ReactCommon/fabric/components/art/state/ARTShape.cpp new file mode 100644 index 00000000000000..31fac9ffeb3e79 --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTShape.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +namespace facebook { +namespace react { + +bool ARTShape::operator==(const ARTElement &rhs) const { + if (rhs.elementType != ARTElementType::Shape) { + return false; + } + auto shape = (const ARTShape &)(rhs); + return std::tie( + elementType, + opacity, + transform, + d, + stroke, + strokeDash, + fill, + strokeWidth, + strokeCap, + strokeJoin) == + std::tie( + shape.elementType, + shape.opacity, + shape.transform, + shape.d, + shape.stroke, + shape.strokeDash, + shape.fill, + shape.strokeWidth, + shape.strokeCap, + shape.strokeJoin); +} + +bool ARTShape::operator!=(const ARTElement &rhs) const { + return !(*this == rhs); +} + +#ifdef ANDROID +folly::dynamic ARTShape::getDynamic() const { + return toDynamic(*this); +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTShape.h b/ReactCommon/fabric/components/art/state/ARTShape.h new file mode 100644 index 00000000000000..e50cf966a061ed --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTShape.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +/* + * Simple, cross-platfrom, React-specific implementation of ART Shape Element + */ +class ARTShape : public ARTElement { + public: + using Shared = std::shared_ptr; + ARTShape( + Float opacity, + std::vector transform, + std::vector d, + std::vector stroke, + std::vector strokeDash, + std::vector fill, + Float strokeWidth, + int strokeCap, + int strokeJoin) + : ARTElement(ARTElementType::Shape, opacity, transform), + d(d), + stroke(stroke), + strokeDash(strokeDash), + fill(fill), + strokeWidth(strokeWidth), + strokeCap(strokeCap), + strokeJoin(strokeJoin){}; + ARTShape() = default; + virtual ~ARTShape(){}; + + std::vector d{}; + std::vector stroke{}; + std::vector strokeDash{}; + std::vector fill{}; + Float strokeWidth{1.0}; + int strokeCap{1}; + int strokeJoin{1}; + + bool operator==(const ARTElement &rhs) const override; + bool operator!=(const ARTElement &rhs) const override; + +#ifdef ANDROID + folly::dynamic getDynamic() const override; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTText.cpp b/ReactCommon/fabric/components/art/state/ARTText.cpp new file mode 100644 index 00000000000000..1a061554134e77 --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTText.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +namespace facebook { +namespace react { + +bool ARTText::operator==(const ARTElement &rhs) const { + if (rhs.elementType != ARTElementType::Text) { + return false; + } + auto text = (const ARTText &)(rhs); + return std::tie( + elementType, + opacity, + transform, + d, + stroke, + strokeDash, + fill, + strokeWidth, + strokeCap, + strokeJoin, + alignment, + frame) == + std::tie( + text.elementType, + text.opacity, + text.transform, + text.d, + text.stroke, + text.strokeDash, + text.fill, + text.strokeWidth, + text.strokeCap, + text.strokeJoin, + text.alignment, + text.frame); +} + +bool ARTText::operator!=(const ARTElement &rhs) const { + return !(*this == rhs); +} + +#ifdef ANDROID +folly::dynamic ARTText::getDynamic() const { + return toDynamic(*this); +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTText.h b/ReactCommon/fabric/components/art/state/ARTText.h new file mode 100644 index 00000000000000..b01da694a377b4 --- /dev/null +++ b/ReactCommon/fabric/components/art/state/ARTText.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace react { + +/* + * Simple, cross-platfrom, React-specific implementation of ART Text Element + */ +class ARTText : public ARTShape { + public: + using ARTShared = std::shared_ptr; + ARTText( + Float opacity, + std::vector transform, + std::vector d, + std::vector stroke, + std::vector strokeDash, + std::vector fill, + Float strokeWidth, + int strokeCap, + int strokeJoin, + ARTTextAlignment alignment, + ARTTextFrame frame) + : ARTShape( + opacity, + transform, + d, + stroke, + strokeDash, + fill, + strokeWidth, + strokeCap, + strokeJoin), + alignment(alignment), + frame(frame) { + elementType = ARTElementType::Text; + }; + ARTText() = default; + virtual ~ARTText(){}; + + bool operator==(const ARTElement &rhs) const override; + bool operator!=(const ARTElement &rhs) const override; + + ARTTextAlignment alignment{ARTTextAlignment::Default}; + ARTTextFrame frame{}; + +#ifdef ANDROID + folly::dynamic getDynamic() const override; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/primitives.h b/ReactCommon/fabric/components/art/state/primitives.h new file mode 100644 index 00000000000000..677fcf9ce1120e --- /dev/null +++ b/ReactCommon/fabric/components/art/state/primitives.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +enum class ARTElementType { Shape, Text, Group }; + +enum class ARTTextAlignment { Default, Right, Center }; + +struct ARTTextFrameFont { + Float fontSize; + std::string fontStyle; + std::string fontFamily; + std::string fontWeight; + + bool operator==(const ARTTextFrameFont &rhs) const { + return std::tie( + this->fontSize, + this->fontStyle, + this->fontFamily, + this->fontWeight) == + std::tie(rhs.fontSize, rhs.fontStyle, rhs.fontFamily, rhs.fontWeight); + } + + bool operator!=(const ARTTextFrameFont &rhs) const { + return !(*this == rhs); + } +}; + +struct ARTTextFrame { + std::vector lines; + ARTTextFrameFont font; + + bool operator==(const ARTTextFrame &rhs) const { + return std::tie(this->lines, this->font) == std::tie(rhs.lines, rhs.font); + } + + bool operator!=(const ARTTextFrame &rhs) const { + return !(*this == rhs); + } +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewComponentDescriptor.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewComponentDescriptor.h new file mode 100644 index 00000000000000..c48ee29d468fd2 --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewComponentDescriptor.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using ARTSurfaceViewComponentDescriptor = + ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.cpp b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.cpp new file mode 100644 index 00000000000000..6969d93f1d29cd --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +namespace facebook { +namespace react { + +ARTSurfaceViewProps::ARTSurfaceViewProps( + ARTSurfaceViewProps const &sourceProps, + RawProps const &rawProps) + : ViewProps(sourceProps, rawProps), + + backgroundColor(convertRawProp( + rawProps, + "backgroundColor", + sourceProps.backgroundColor, + {})) {} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.h new file mode 100644 index 00000000000000..c243686d506bb8 --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class ARTSurfaceViewProps final : public ViewProps { + public: + ARTSurfaceViewProps() = default; + ARTSurfaceViewProps( + ARTSurfaceViewProps const &sourceProps, + RawProps const &rawProps); + +#pragma mark - Props + + SharedColor backgroundColor{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.cpp b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.cpp new file mode 100644 index 00000000000000..5be367ba3b68b0 --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +using Content = ARTSurfaceViewShadowNode::Content; + +extern const char ARTSurfaceViewComponentName[] = "ARTSurfaceView"; + +void ARTSurfaceViewShadowNode::layout(LayoutContext layoutContext) { + ensureUnsealed(); + auto content = getContent(); + updateStateIfNeeded(content); +} + +Content const &ARTSurfaceViewShadowNode::getContent() const { + if (content_.has_value()) { + return content_.value(); + } + ensureUnsealed(); + auto elements = ARTElement::ListOfShared{}; + ARTBaseShadowNode::buildElements(*this, elements); + content_ = Content{elements}; + return content_.value(); +} + +void ARTSurfaceViewShadowNode::updateStateIfNeeded(Content const &content) { + ensureUnsealed(); + auto &state = getStateData(); + if (content.elements == state.elements) { + return; + } + setStateData(ARTSurfaceViewState{content.elements}); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.h new file mode 100644 index 00000000000000..0bd2cdaa43fb1e --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char ARTSurfaceViewComponentName[]; + +using ARTSurfaceViewEventEmitter = ViewEventEmitter; + +/* + * `ShadowNode` for component, represents -like + * component containing that will be used to display ARTElements. + */ +class ARTSurfaceViewShadowNode : public ConcreteViewShadowNode< + ARTSurfaceViewComponentName, + ARTSurfaceViewProps, + ARTSurfaceViewEventEmitter, + ARTSurfaceViewState> { + public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + + static ShadowNodeTraits BaseTraits() { + auto traits = ConcreteViewShadowNode::BaseTraits(); + traits.set(ShadowNodeTraits::Trait::LeafYogaNode); + return traits; + } + + class Content final { + public: + ARTElement::ListOfShared elements{}; + }; + + void layout(LayoutContext layoutContext) override; + + private: + Content const &getContent() const; + + void updateStateIfNeeded(Content const &content); + + /* + * Cached content of the subtree started from the node. + */ + mutable better::optional content_{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.cpp b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.cpp new file mode 100644 index 00000000000000..44e47d45c04646 --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +namespace facebook { +namespace react { + +#ifdef ANDROID +folly::dynamic ARTSurfaceViewState::getDynamic() const { + return toDynamic(*this); +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.h new file mode 100644 index 00000000000000..86eb3d0f5e8c58 --- /dev/null +++ b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include + +#ifdef ANDROID +#include +#endif + +namespace facebook { +namespace react { + +/* + * State for component. + * Represents what to render and how to render. + */ +class ARTSurfaceViewState final { + public: + ARTElement::ListOfShared elements{}; + +#ifdef ANDROID + ARTSurfaceViewState(ARTElement::ListOfShared const &elements) + : elements(elements) {} + ARTSurfaceViewState() = default; + ARTSurfaceViewState( + ARTSurfaceViewState const &previousState, + folly::dynamic const &data) { + assert(false && "Not supported"); + }; + folly::dynamic getDynamic() const; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextComponentDescriptor.h b/ReactCommon/fabric/components/art/text/ARTTextComponentDescriptor.h new file mode 100644 index 00000000000000..ae839d71e40925 --- /dev/null +++ b/ReactCommon/fabric/components/art/text/ARTTextComponentDescriptor.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +using ARTTextComponentDescriptor = + ConcreteComponentDescriptor; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextProps.cpp b/ReactCommon/fabric/components/art/text/ARTTextProps.cpp new file mode 100644 index 00000000000000..b7948e9a8feaec --- /dev/null +++ b/ReactCommon/fabric/components/art/text/ARTTextProps.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +namespace facebook { +namespace react { + +ARTTextProps::ARTTextProps( + const ARTTextProps &sourceProps, + const RawProps &rawProps) + : Props(sourceProps, rawProps), + + opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, {1.0})), + transform( + convertRawProp(rawProps, "transform", sourceProps.transform, {})), + d(convertRawProp(rawProps, "d", sourceProps.d, {})), + stroke(convertRawProp(rawProps, "stroke", sourceProps.stroke, {})), + strokeDash( + convertRawProp(rawProps, "strokeDash", sourceProps.strokeDash, {})), + fill(convertRawProp(rawProps, "fill", sourceProps.fill, {})), + strokeWidth(convertRawProp( + rawProps, + "strokeWidth", + sourceProps.strokeWidth, + {1.0})), + strokeCap( + convertRawProp(rawProps, "strokeCap", sourceProps.strokeCap, {1})), + strokeJoin( + convertRawProp(rawProps, "strokeJoin", sourceProps.strokeJoin, {1})), + alignment( + convertRawProp(rawProps, "alignment", sourceProps.alignment, {})), + frame(convertRawProp(rawProps, "frame", sourceProps.frame, {})){}; + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE +SharedDebugStringConvertibleList RawTextProps::getDebugProps() const { + return {debugStringConvertibleItem("opacity", opacity)}; +} +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextProps.h b/ReactCommon/fabric/components/art/text/ARTTextProps.h new file mode 100644 index 00000000000000..087e56669cf1ef --- /dev/null +++ b/ReactCommon/fabric/components/art/text/ARTTextProps.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +static inline void fromRawValue( + const RawValue &value, + ARTTextFrameFont &result) { + auto map = (better::map)value; + auto fontSize = map.find("fontSize"); + if (fontSize != map.end()) { + fromRawValue(fontSize->second, result.fontSize); + } + auto fontStyle = map.find("fontStyle"); + if (fontStyle != map.end()) { + fromRawValue(fontStyle->second, result.fontStyle); + } + auto fontFamily = map.find("fontFamily"); + if (fontFamily != map.end()) { + fromRawValue(fontFamily->second, result.fontFamily); + } + auto fontWeight = map.find("fontWeight"); + if (fontWeight != map.end()) { + fromRawValue(fontWeight->second, result.fontWeight); + } +} + +static inline void fromRawValue( + const RawValue &value, + ARTTextAlignment &result) { + auto alignment = (int)value; + switch (alignment) { + case 1: + result = ARTTextAlignment::Right; + break; + case 2: + result = ARTTextAlignment::Center; + break; + default: + result = ARTTextAlignment::Default; + break; + } +} + +static inline std::string toString(const ARTTextFrameFont &value) { + return "[Object ARTTextFrameFont]"; +} + +static inline void fromRawValue(const RawValue &value, ARTTextFrame &result) { + auto map = (better::map)value; + + auto lines = map.find("lines"); + if (lines != map.end()) { + fromRawValue(lines->second, result.lines); + } + auto font = map.find("font"); + if (font != map.end()) { + fromRawValue(font->second, result.font); + } +} + +static inline std::string toString(const ARTTextFrame &value) { + return "[Object ARTTextFrame]"; +} + +class ARTTextProps; + +using SharedARTTextProps = std::shared_ptr; + +class ARTTextProps : public Props { + public: + ARTTextProps() = default; + ARTTextProps(const ARTTextProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + Float opacity{1.0}; + std::vector transform{}; + std::vector d{}; + std::vector stroke{}; + std::vector strokeDash{}; + std::vector fill{}; + Float strokeWidth{1.0}; + int strokeCap{1}; + int strokeJoin{1}; + ARTTextAlignment alignment{ARTTextAlignment::Default}; + ARTTextFrame frame{}; + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE + SharedDebugStringConvertibleList getDebugProps() const override; +#endif +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextShadowNode.cpp b/ReactCommon/fabric/components/art/text/ARTTextShadowNode.cpp new file mode 100644 index 00000000000000..aeb596e13348d3 --- /dev/null +++ b/ReactCommon/fabric/components/art/text/ARTTextShadowNode.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "ARTTextShadowNode.h" +#include +#include + +namespace facebook { +namespace react { + +extern const char ARTTextComponentName[] = "ARTText"; + +ARTElement::Shared ARTTextShadowNode::getARTElement() const { + auto props = getConcreteProps(); + return std::make_shared( + props.opacity, + props.transform, + props.d, + props.stroke, + props.strokeDash, + props.fill, + props.strokeWidth, + props.strokeCap, + props.strokeJoin, + props.alignment, + props.frame); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextShadowNode.h b/ReactCommon/fabric/components/art/text/ARTTextShadowNode.h new file mode 100644 index 00000000000000..93d3d4a6567ab1 --- /dev/null +++ b/ReactCommon/fabric/components/art/text/ARTTextShadowNode.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char ARTTextComponentName[]; + +/* + * `ShadowNode` for component. + */ +class ARTTextShadowNode + : public ConcreteShadowNode, + public ARTBaseShadowNode { + public: + using ConcreteShadowNode::ConcreteShadowNode; + + virtual ARTElement::Shared getARTElement() const override; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/image/BUCK b/ReactCommon/fabric/components/image/BUCK index 9f94284cf15630..43b8f54739565a 100644 --- a/ReactCommon/fabric/components/image/BUCK +++ b/ReactCommon/fabric/components/image/BUCK @@ -35,10 +35,9 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/image/ImageShadowNode.cpp b/ReactCommon/fabric/components/image/ImageShadowNode.cpp index 68d0667aa4e846..4a00f2c1bf23ca 100644 --- a/ReactCommon/fabric/components/image/ImageShadowNode.cpp +++ b/ReactCommon/fabric/components/image/ImageShadowNode.cpp @@ -45,19 +45,23 @@ void ImageShadowNode::updateStateIfNeeded() { ImageSource ImageShadowNode::getImageSource() const { auto sources = getConcreteProps().sources; - if (sources.size() == 0) { + if (sources.empty()) { return { /* .type = */ ImageSource::Type::Invalid, }; } - if (sources.size() == 1) { - return sources[0]; - } - auto layoutMetrics = getLayoutMetrics(); auto size = layoutMetrics.getContentFrame().size; auto scale = layoutMetrics.pointScaleFactor; + + if (sources.size() == 1) { + auto source = sources[0]; + source.size = size; + source.scale = scale; + return source; + } + auto targetImageArea = size.width * size.height * scale * scale; auto bestFit = std::numeric_limits::infinity(); @@ -77,6 +81,9 @@ ImageSource ImageShadowNode::getImageSource() const { } } + bestSource.size = size; + bestSource.scale = scale; + return bestSource; } diff --git a/ReactCommon/fabric/components/legacyviewmanagerinterop/BUCK b/ReactCommon/fabric/components/legacyviewmanagerinterop/BUCK index 3c1e5c09748c30..3cd958bb17889e 100644 --- a/ReactCommon/fabric/components/legacyviewmanagerinterop/BUCK +++ b/ReactCommon/fabric/components/legacyviewmanagerinterop/BUCK @@ -34,9 +34,9 @@ rn_xplat_cxx_library( "-Wall", ], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm b/ReactCommon/fabric/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm index 7e2c3813676cf0..a5921741440945 100644 --- a/ReactCommon/fabric/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm +++ b/ReactCommon/fabric/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm @@ -24,6 +24,10 @@ if (componentName == "StickerInputView") { return "FBStickerInputViewManager"; } + + if (componentName == "FBRotatablePhotoPlayer") { + return "FBRotatablePhotoPlayerViewManager"; + } std::string fbPrefix("FB"); if (std::mismatch(fbPrefix.begin(), fbPrefix.end(), componentName.begin()).first == fbPrefix.end()) { // If `moduleName` has "FB" prefix. diff --git a/ReactCommon/fabric/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm b/ReactCommon/fabric/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm index 1874f8d5d1c45f..d8f1b4abf4ed7b 100644 --- a/ReactCommon/fabric/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm +++ b/ReactCommon/fabric/components/legacyviewmanagerinterop/RCTLegacyViewManagerInteropCoordinator.mm @@ -20,7 +20,7 @@ @implementation RCTLegacyViewManagerInteropCoordinator { RCTComponentData *_componentData; - RCTBridge *_bridge; + __weak RCTBridge *_bridge; /* Each instnace of `RCTLegacyViewManagerInteropComponentView` registers a block to which events are dispatched. This is the container that maps unretained UIView pointer to a block to which the event is dispatched. diff --git a/ReactCommon/fabric/components/modal/BUCK b/ReactCommon/fabric/components/modal/BUCK index a84b80df2a456e..5913667729910f 100644 --- a/ReactCommon/fabric/components/modal/BUCK +++ b/ReactCommon/fabric/components/modal/BUCK @@ -48,12 +48,10 @@ rn_xplat_cxx_library( fbandroid_headers = glob( ["platform/android/*.h"], ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( ["platform/android/*.cpp"], ), fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, ios_exported_headers = subdir_glob( @@ -63,6 +61,7 @@ rn_xplat_cxx_library( ], prefix = "react/components/modal", ), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/picker/BUCK b/ReactCommon/fabric/components/picker/BUCK index 8508262ce704d0..280aed2d07ccbc 100644 --- a/ReactCommon/fabric/components/picker/BUCK +++ b/ReactCommon/fabric/components/picker/BUCK @@ -44,11 +44,10 @@ rn_xplat_cxx_library( fbandroid_deps = [ react_native_target("jni/react/jni:jni"), ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/root/BUCK b/ReactCommon/fabric/components/root/BUCK index 5805cff81df76a..bef67507464974 100644 --- a/ReactCommon/fabric/components/root/BUCK +++ b/ReactCommon/fabric/components/root/BUCK @@ -35,10 +35,9 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/safeareaview/BUCK b/ReactCommon/fabric/components/safeareaview/BUCK index 4edd42c67573da..033f987770f11d 100644 --- a/ReactCommon/fabric/components/safeareaview/BUCK +++ b/ReactCommon/fabric/components/safeareaview/BUCK @@ -31,9 +31,9 @@ rn_xplat_cxx_library( "-Wall", ], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.cpp b/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.cpp index a04bea2e5e6edd..bf750e84f504af 100644 --- a/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.cpp +++ b/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.cpp @@ -12,5 +12,13 @@ namespace react { extern const char SafeAreaViewComponentName[] = "SafeAreaView"; +SafeAreaViewShadowNode::SafeAreaViewShadowNode( + ShadowNode const &sourceShadowNode, + ShadowNodeFragment const &fragment) + : ConcreteViewShadowNode(sourceShadowNode, fragment), + alreadyAppliedPadding( + static_cast(sourceShadowNode) + .alreadyAppliedPadding) {} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.h b/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.h index 27df74ffaba387..1f09c463b63606 100644 --- a/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.h +++ b/ReactCommon/fabric/components/safeareaview/SafeAreaViewShadowNode.h @@ -29,6 +29,10 @@ class SafeAreaViewShadowNode final : public ConcreteViewShadowNode< public: EdgeInsets alreadyAppliedPadding{}; + + SafeAreaViewShadowNode( + ShadowNode const &sourceShadowNode, + ShadowNodeFragment const &fragment); }; } // namespace react diff --git a/ReactCommon/fabric/components/scrollview/BUCK b/ReactCommon/fabric/components/scrollview/BUCK index 5f59cfba1a4768..8ffd81f4003892 100644 --- a/ReactCommon/fabric/components/scrollview/BUCK +++ b/ReactCommon/fabric/components/scrollview/BUCK @@ -38,11 +38,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.cpp b/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.cpp index b8c7e708616a62..a44150d2183fd6 100644 --- a/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.cpp +++ b/ReactCommon/fabric/components/scrollview/ScrollViewEventEmitter.cpp @@ -60,7 +60,9 @@ static jsi::Value scrollViewMetricsPayload( void ScrollViewEventEmitter::onScroll( const ScrollViewMetrics &scrollViewMetrics) const { - dispatchScrollViewEvent("scroll", scrollViewMetrics); + dispatchUniqueEvent("scroll", [scrollViewMetrics](jsi::Runtime &runtime) { + return scrollViewMetricsPayload(runtime, scrollViewMetrics); + }); } void ScrollViewEventEmitter::onScrollBeginDrag( diff --git a/ReactCommon/fabric/components/scrollview/ScrollViewProps.cpp b/ReactCommon/fabric/components/scrollview/ScrollViewProps.cpp index fdc3c113e20b51..fdedc739e7c058 100644 --- a/ReactCommon/fabric/components/scrollview/ScrollViewProps.cpp +++ b/ReactCommon/fabric/components/scrollview/ScrollViewProps.cpp @@ -17,8 +17,8 @@ namespace facebook { namespace react { ScrollViewProps::ScrollViewProps( - const ScrollViewProps &sourceProps, - const RawProps &rawProps) + ScrollViewProps const &sourceProps, + RawProps const &rawProps) : ViewProps(sourceProps, rawProps), alwaysBounceHorizontal(convertRawProp( rawProps, @@ -126,6 +126,11 @@ ScrollViewProps::ScrollViewProps( "contentInset", sourceProps.contentInset, {})), + contentOffset(convertRawProp( + rawProps, + "contentOffset", + sourceProps.contentOffset, + {})), scrollIndicatorInsets(convertRawProp( rawProps, "scrollIndicatorInsets", @@ -232,6 +237,10 @@ SharedDebugStringConvertibleList ScrollViewProps::getDebugProps() const { "contentInset", contentInset, defaultScrollViewProps.contentInset), + debugStringConvertibleItem( + "contentOffset", + contentOffset, + defaultScrollViewProps.contentOffset), debugStringConvertibleItem( "scrollIndicatorInsets", scrollIndicatorInsets, diff --git a/ReactCommon/fabric/components/scrollview/ScrollViewProps.h b/ReactCommon/fabric/components/scrollview/ScrollViewProps.h index 14e5c5a88bd9bc..e520c1736347bc 100644 --- a/ReactCommon/fabric/components/scrollview/ScrollViewProps.h +++ b/ReactCommon/fabric/components/scrollview/ScrollViewProps.h @@ -17,35 +17,36 @@ namespace react { class ScrollViewProps final : public ViewProps { public: ScrollViewProps() = default; - ScrollViewProps(const ScrollViewProps &sourceProps, const RawProps &rawProps); + ScrollViewProps(ScrollViewProps const &sourceProps, RawProps const &rawProps); #pragma mark - Props - const bool alwaysBounceHorizontal{}; - const bool alwaysBounceVertical{}; - const bool bounces{true}; - const bool bouncesZoom{true}; - const bool canCancelContentTouches{true}; - const bool centerContent{}; - const bool automaticallyAdjustContentInsets{}; - const Float decelerationRate{0.998}; - const bool directionalLockEnabled{}; - const ScrollViewIndicatorStyle indicatorStyle{}; - const ScrollViewKeyboardDismissMode keyboardDismissMode{}; - const Float maximumZoomScale{1.0}; - const Float minimumZoomScale{1.0}; - const bool scrollEnabled{true}; - const bool pagingEnabled{}; - const bool pinchGestureEnabled{true}; - const bool scrollsToTop{true}; - const bool showsHorizontalScrollIndicator{true}; - const bool showsVerticalScrollIndicator{true}; - const Float scrollEventThrottle{}; - const Float zoomScale{1.0}; - const EdgeInsets contentInset{}; - const EdgeInsets scrollIndicatorInsets{}; - const Float snapToInterval{}; - const ScrollViewSnapToAlignment snapToAlignment{}; + bool const alwaysBounceHorizontal{}; + bool const alwaysBounceVertical{}; + bool const bounces{true}; + bool const bouncesZoom{true}; + bool const canCancelContentTouches{true}; + bool const centerContent{}; + bool const automaticallyAdjustContentInsets{}; + Float const decelerationRate{0.998}; + bool const directionalLockEnabled{}; + ScrollViewIndicatorStyle const indicatorStyle{}; + ScrollViewKeyboardDismissMode const keyboardDismissMode{}; + Float const maximumZoomScale{1.0}; + Float const minimumZoomScale{1.0}; + bool const scrollEnabled{true}; + bool const pagingEnabled{}; + bool const pinchGestureEnabled{true}; + bool const scrollsToTop{true}; + bool const showsHorizontalScrollIndicator{true}; + bool const showsVerticalScrollIndicator{true}; + Float const scrollEventThrottle{}; + Float const zoomScale{1.0}; + EdgeInsets const contentInset{}; + Point const contentOffset{}; + EdgeInsets const scrollIndicatorInsets{}; + Float const snapToInterval{}; + ScrollViewSnapToAlignment const snapToAlignment{}; #pragma mark - DebugStringConvertible diff --git a/ReactCommon/fabric/components/slider/BUCK b/ReactCommon/fabric/components/slider/BUCK index 41ce2c68985eb3..4f736a12d5160c 100644 --- a/ReactCommon/fabric/components/slider/BUCK +++ b/ReactCommon/fabric/components/slider/BUCK @@ -53,12 +53,10 @@ rn_xplat_cxx_library( fbandroid_headers = glob( ["platform/android/*.h"], ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( ["platform/android/*.cpp"], ), fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, ios_exported_headers = subdir_glob( @@ -74,6 +72,7 @@ rn_xplat_cxx_library( ios_srcs = glob( ["platform/ios/*.cpp"], ), + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/switch/BUCK b/ReactCommon/fabric/components/switch/BUCK index 8d12a6a070fa0d..c3a23b7811836c 100644 --- a/ReactCommon/fabric/components/switch/BUCK +++ b/ReactCommon/fabric/components/switch/BUCK @@ -44,11 +44,10 @@ rn_xplat_cxx_library( fbandroid_deps = [ react_native_target("jni/react/jni:jni"), ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/text/BUCK b/ReactCommon/fabric/components/text/BUCK index a1271eb16c66ec..f7d5ded2085661 100644 --- a/ReactCommon/fabric/components/text/BUCK +++ b/ReactCommon/fabric/components/text/BUCK @@ -43,11 +43,10 @@ rn_xplat_cxx_library( "-Wall", ], cxx_tests = [":tests"], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/text/basetext/BaseTextProps.h b/ReactCommon/fabric/components/text/basetext/BaseTextProps.h index ba9c7fe4058171..5554277f2daa1a 100644 --- a/ReactCommon/fabric/components/text/basetext/BaseTextProps.h +++ b/ReactCommon/fabric/components/text/basetext/BaseTextProps.h @@ -26,7 +26,7 @@ class BaseTextProps { #pragma mark - Props - const TextAttributes textAttributes{}; + TextAttributes textAttributes{}; #pragma mark - DebugStringConvertible (partially) diff --git a/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h b/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h index 0e04b3c2b998b9..be95f4cacf1af6 100644 --- a/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h +++ b/ReactCommon/fabric/components/text/basetext/BaseTextShadowNode.h @@ -57,6 +57,13 @@ class BaseTextShadowNode { ShadowNode const &parentNode, AttributedString &outAttributedString, Attachments &outAttachments); + + /** + * Returns a character used to measure empty strings in native platforms. + */ + inline static std::string getEmptyPlaceholder() { + return "I"; + } }; } // namespace react diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h b/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h index d360c93d339d13..0ec89c568312f8 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphComponentDescriptor.h @@ -9,6 +9,7 @@ #include "ParagraphShadowNode.h" +#include #include #include #include @@ -30,6 +31,18 @@ class ParagraphComponentDescriptor final textLayoutManager_ = std::make_shared(contextContainer_); } + virtual SharedProps interpolateProps( + float animationProgress, + const SharedProps &props, + const SharedProps &newProps) const override { + SharedProps interpolatedPropsShared = cloneProps(newProps, {}); + + interpolateViewProps( + animationProgress, props, newProps, interpolatedPropsShared); + + return interpolatedPropsShared; + }; + protected: void adopt(UnsharedShadowNode shadowNode) const override { ConcreteComponentDescriptor::adopt(shadowNode); diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphProps.cpp b/ReactCommon/fabric/components/text/paragraph/ParagraphProps.cpp index 672ad29d787c92..fc731707084b53 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphProps.cpp +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphProps.cpp @@ -28,7 +28,14 @@ ParagraphProps::ParagraphProps( rawProps, "selectable", sourceProps.isSelectable, - {})){}; + {})) { + /* + * These props are applied to `View`, therefore they must not be a part of + * base text attributes. + */ + textAttributes.opacity = std::numeric_limits::quiet_NaN(); + textAttributes.backgroundColor = {}; +}; #pragma mark - DebugStringConvertible diff --git a/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp b/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp index b93503a42edd58..3ead9c9a4bf897 100644 --- a/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp +++ b/ReactCommon/fabric/components/text/paragraph/ParagraphShadowNode.cpp @@ -120,13 +120,21 @@ Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const { auto content = getContentWithMeasuredAttachments(LayoutContext{}, layoutConstraints); - if (content.attributedString.isEmpty()) { - return layoutConstraints.clamp({0, 0}); + auto attributedString = content.attributedString; + if (attributedString.isEmpty()) { + // Note: `zero-width space` is insufficient in some cases (e.g. when we need + // to measure the "height" of the font). + // TODO T67606511: We will redefine the measurement of empty strings as part + // of T67606511 + auto string = BaseTextShadowNode::getEmptyPlaceholder(); + auto textAttributes = TextAttributes::defaultTextAttributes(); + textAttributes.apply(getConcreteProps().textAttributes); + attributedString.appendFragment({string, textAttributes, {}}); } return textLayoutManager_ ->measure( - AttributedStringBox{content.attributedString}, + AttributedStringBox{attributedString}, content.paragraphAttributes, layoutConstraints) .size; diff --git a/ReactCommon/fabric/components/textinput/BUCK b/ReactCommon/fabric/components/textinput/BUCK index 616d7c0468e780..49df25270c9066 100644 --- a/ReactCommon/fabric/components/textinput/BUCK +++ b/ReactCommon/fabric/components/textinput/BUCK @@ -40,11 +40,10 @@ rn_xplat_cxx_library( "-Wall", ], cxx_tests = [":tests"], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp index 0d90987f511cc3..1349f3f4e88654 100644 --- a/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp +++ b/ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp @@ -62,6 +62,8 @@ AttributedString AndroidTextInputShadowNode::getAttributedString() const { // single character in the string so that the measured height is greater // than zero. Otherwise, empty TextInputs with no placeholder don't // display at all. +// TODO T67606511: We will redefine the measurement of empty strings as part +// of T67606511 AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString() const { // Return placeholder text, since text and children are empty. @@ -70,7 +72,7 @@ AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString() fragment.string = getConcreteProps().placeholder; if (fragment.string.empty()) { - fragment.string = " "; + fragment.string = BaseTextShadowNode::getEmptyPlaceholder(); } auto textAttributes = TextAttributes::defaultTextAttributes(); diff --git a/ReactCommon/fabric/components/textinput/iostextinput/BUCK b/ReactCommon/fabric/components/textinput/iostextinput/BUCK index dfe4ccdf3df86e..f432474e0b2f9a 100644 --- a/ReactCommon/fabric/components/textinput/iostextinput/BUCK +++ b/ReactCommon/fabric/components/textinput/iostextinput/BUCK @@ -41,9 +41,9 @@ rn_xplat_cxx_library( ], cxx_tests = [":tests"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp b/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp index 5b65e24a325bea..fd70e306d73135 100644 --- a/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp +++ b/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp @@ -38,7 +38,11 @@ AttributedStringBox TextInputShadowNode::attributedStringBoxToMeasure() const { auto placeholder = getConcreteProps().placeholder; // Note: `zero-width space` is insufficient in some cases (e.g. when we need // to measure the "hight" of the font). - auto string = !placeholder.empty() ? placeholder : "I"; + // TODO T67606511: We will redefine the measurement of empty strings as part + // of T67606511 + auto string = !placeholder.empty() + ? placeholder + : BaseTextShadowNode::getEmptyPlaceholder(); auto textAttributes = getConcreteProps().getEffectiveTextAttributes(); attributedString.appendFragment({string, textAttributes, {}}); } diff --git a/ReactCommon/fabric/components/textinput/iostextinput/primitives.h b/ReactCommon/fabric/components/textinput/iostextinput/primitives.h index 1bd7caa9386bd3..f10fa2b42e368b 100644 --- a/ReactCommon/fabric/components/textinput/iostextinput/primitives.h +++ b/ReactCommon/fabric/components/textinput/iostextinput/primitives.h @@ -180,6 +180,12 @@ class TextInputTraits final { */ KeyboardType keyboardType{KeyboardType::Default}; + /* + * iOS & Android + * Default value: `true`. + */ + bool showSoftInputOnFocus{true}; + /* * Some values iOS- or Android-only (inherently particular-OS-specific) * Default value: `Default`. diff --git a/ReactCommon/fabric/components/textinput/iostextinput/propsConversions.h b/ReactCommon/fabric/components/textinput/iostextinput/propsConversions.h index 6c12a5716a15ab..6b5150d0aae095 100644 --- a/ReactCommon/fabric/components/textinput/iostextinput/propsConversions.h +++ b/ReactCommon/fabric/components/textinput/iostextinput/propsConversions.h @@ -88,6 +88,11 @@ static TextInputTraits convertRawProp( "keyboardType", sourceTraits.keyboardType, defaultTraits.keyboardType); + traits.showSoftInputOnFocus = convertRawProp( + rawProps, + "showSoftInputOnFocus", + sourceTraits.showSoftInputOnFocus, + defaultTraits.showSoftInputOnFocus); traits.returnKeyType = convertRawProp( rawProps, "returnKeyType", diff --git a/ReactCommon/fabric/components/unimplementedview/BUCK b/ReactCommon/fabric/components/unimplementedview/BUCK index aae773953d7f69..001f9607cdc532 100644 --- a/ReactCommon/fabric/components/unimplementedview/BUCK +++ b/ReactCommon/fabric/components/unimplementedview/BUCK @@ -36,8 +36,8 @@ rn_xplat_cxx_library( "-Wall", ], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/view/BUCK b/ReactCommon/fabric/components/view/BUCK index eb8a63fe20375a..49b6f31659831c 100644 --- a/ReactCommon/fabric/components/view/BUCK +++ b/ReactCommon/fabric/components/view/BUCK @@ -41,11 +41,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/components/view/ViewComponentDescriptor.h b/ReactCommon/fabric/components/view/ViewComponentDescriptor.h index 34cb8107a357a8..006081daf3d2de 100644 --- a/ReactCommon/fabric/components/view/ViewComponentDescriptor.h +++ b/ReactCommon/fabric/components/view/ViewComponentDescriptor.h @@ -9,11 +9,30 @@ #include #include +#include "ViewProps.h" +#include "ViewPropsInterpolation.h" namespace facebook { namespace react { -using ViewComponentDescriptor = ConcreteComponentDescriptor; +class ViewComponentDescriptor + : public ConcreteComponentDescriptor { + public: + ViewComponentDescriptor(ComponentDescriptorParameters const ¶meters) + : ConcreteComponentDescriptor(parameters) {} + + virtual SharedProps interpolateProps( + float animationProgress, + const SharedProps &props, + const SharedProps &newProps) const override { + SharedProps interpolatedPropsShared = cloneProps(newProps, {}); + + interpolateViewProps( + animationProgress, props, newProps, interpolatedPropsShared); + + return interpolatedPropsShared; + }; +}; } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/view/ViewPropsInterpolation.h b/ReactCommon/fabric/components/view/ViewPropsInterpolation.h new file mode 100644 index 00000000000000..0470d696d514cc --- /dev/null +++ b/ReactCommon/fabric/components/view/ViewPropsInterpolation.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "ViewProps.h" + +namespace facebook { +namespace react { + +/** + * Given animation progress, old props, new props, and an "interpolated" shared + * props struct, this will mutate the "interpolated" struct in-place to give it + * values interpolated between the old and new props. + */ +static inline void interpolateViewProps( + Float animationProgress, + const SharedProps &oldPropsShared, + const SharedProps &newPropsShared, + SharedProps &interpolatedPropsShared) { + ViewProps const *oldViewProps = + dynamic_cast(oldPropsShared.get()); + ViewProps const *newViewProps = + dynamic_cast(newPropsShared.get()); + ViewProps *interpolatedProps = const_cast( + dynamic_cast(interpolatedPropsShared.get())); + + assert( + oldViewProps != nullptr && newViewProps != nullptr && + interpolatedProps != nullptr); + + interpolatedProps->opacity = oldViewProps->opacity + + (newViewProps->opacity - oldViewProps->opacity) * animationProgress; + + interpolatedProps->transform = Transform::Interpolate( + animationProgress, oldViewProps->transform, newViewProps->transform); + + // Android uses RawProps, not props, to update props on the platform... + // Since interpolated props don't interpolate at all using RawProps, we need + // to "re-hydrate" raw props after interpolating. This is what actually gets + // sent to the mounting layer. This is a temporary hack, only for platforms + // that use RawProps/folly::dynamic instead of concrete props on the + // mounting layer. Once we can remove this, we should change `rawProps` to + // be const again. +#ifdef ANDROID + interpolatedProps->rawProps["opacity"] = interpolatedProps->opacity; + + interpolatedProps->rawProps["transform"] = + (folly::dynamic)interpolatedProps->transform; +#endif +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/view/ViewShadowNode.cpp b/ReactCommon/fabric/components/view/ViewShadowNode.cpp index 6f58a7a0b4ad5c..b503bb2986b7cb 100644 --- a/ReactCommon/fabric/components/view/ViewShadowNode.cpp +++ b/ReactCommon/fabric/components/view/ViewShadowNode.cpp @@ -43,8 +43,9 @@ void ViewShadowNode::initialize() noexcept { viewProps.pointerEvents == PointerEventsMode::None || !viewProps.nativeId.empty() || viewProps.accessible || viewProps.opacity != 1.0 || viewProps.transform != Transform{} || - viewProps.zIndex != 0 || viewProps.getClipsContentToBounds() || - viewProps.yogaStyle.positionType() == YGPositionTypeAbsolute || + (viewProps.zIndex != 0 && + viewProps.yogaStyle.positionType() == YGPositionTypeAbsolute) || + viewProps.getClipsContentToBounds() || isColorMeaningful(viewProps.shadowColor); bool formsView = isColorMeaningful(viewProps.backgroundColor) || diff --git a/ReactCommon/fabric/components/view/conversions.h b/ReactCommon/fabric/components/view/conversions.h index f7ad89b65187ed..0ac41614dd0dfc 100644 --- a/ReactCommon/fabric/components/view/conversions.h +++ b/ReactCommon/fabric/components/view/conversions.h @@ -261,7 +261,7 @@ inline void fromRawValue(const RawValue &value, YGAlign &result) { result = YGAlignBaseline; return; } - if (stringValue == "between") { + if (stringValue == "space-between") { result = YGAlignSpaceBetween; return; } diff --git a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp index 04c48fda480595..1a9982b2cf50e1 100644 --- a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp +++ b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp @@ -73,6 +73,7 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( &initializeYogaConfig(yogaConfig_)) { yogaNode_.setContext(this); yogaNode_.setOwner(nullptr); + updateYogaChildrenOwnersIfNeeded(); // Yoga node must inherit dirty flag. assert( @@ -223,6 +224,14 @@ bool YogaLayoutableShadowNode::doesOwn( return child.yogaNode_.getOwner() == &yogaNode_; } +void YogaLayoutableShadowNode::updateYogaChildrenOwnersIfNeeded() { + for (auto &childYogaNode : yogaNode_.getChildren()) { + if (childYogaNode->getOwner() == &yogaNode_) { + childYogaNode->setOwner(reinterpret_cast(0xBADC0FFEE0DDF00D)); + } + } +} + void YogaLayoutableShadowNode::updateYogaChildren() { if (getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) { return; @@ -597,7 +606,7 @@ void YogaLayoutableShadowNode::ensureYogaChildrenOwnersConsistency() const { // The owner might be not equal to the `yogaNode_` though. auto &yogaChildren = yogaNode_.getChildren(); - if (yogaChildren.size() > 0) { + if (!yogaChildren.empty()) { auto owner = yogaChildren.at(0)->getOwner(); for (auto const &child : yogaChildren) { assert(child->getOwner() == owner); @@ -617,7 +626,7 @@ void YogaLayoutableShadowNode::ensureYogaChildrenLookFine() const { for (auto const &yogaChild : yogaChildren) { assert(yogaChild->getContext()); assert(yogaChild->getChildren().size() < 16384); - if (yogaChild->getChildren().size() > 0) { + if (!yogaChild->getChildren().empty()) { assert(!yogaChild->hasMeasureFunc()); } } @@ -635,7 +644,7 @@ void YogaLayoutableShadowNode::ensureYogaChildrenAlighment() const { auto &children = getChildren(); if (getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) { - assert(yogaChildren.size() == 0); + assert(yogaChildren.empty()); return; } diff --git a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h index e9ea29cd477e87..59148e333b80f3 100644 --- a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h +++ b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h @@ -100,6 +100,16 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { mutable YGNode yogaNode_; private: + /* + * Goes over `yogaNode_.getChildren()` and in case child's owner is + * equal to address of `yogaNode_`, it sets child's owner address + * to `0xBADC0FFEE0DDF00D`. This is magic constant, the intention + * is to make debugging easier when the address pops up in debugger. + * This prevents ABA problem where child yoga node goes from owned -> unowned + * -> back to owned because its parent is allocated at the same address. + */ + void updateYogaChildrenOwnersIfNeeded(); + /* * Return true if child's yogaNode's owner is this->yogaNode_. Otherwise * returns false. diff --git a/ReactCommon/fabric/core/BUCK b/ReactCommon/fabric/core/BUCK index 5930a38025e754..948f56912cb165 100644 --- a/ReactCommon/fabric/core/BUCK +++ b/ReactCommon/fabric/core/BUCK @@ -44,11 +44,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h b/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h index c9d2feb75af004..9d47425bc9b2c4 100644 --- a/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h +++ b/ReactCommon/fabric/core/componentdescriptor/ComponentDescriptor.h @@ -104,6 +104,15 @@ class ComponentDescriptor { const SharedProps &props, const RawProps &rawProps) const = 0; + /* + * Creates a new `Props` of a particular type with all values interpolated + * between `props` and `newProps`. + */ + virtual SharedProps interpolateProps( + float animationProgress, + const SharedProps &props, + const SharedProps &newProps) const = 0; + /* * Create an initial State object that represents (and contains) an initial * State's data which can be constructed based on initial Props. diff --git a/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h b/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h index 43fbeaf40cf3ae..e987de792eb575 100644 --- a/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h +++ b/ReactCommon/fabric/core/componentdescriptor/ConcreteComponentDescriptor.h @@ -122,6 +122,14 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { return ShadowNodeT::Props(rawProps, props); }; + virtual SharedProps interpolateProps( + float animationProgress, + const SharedProps &props, + const SharedProps &newProps) const override { + // By default, this does nothing. + return cloneProps(newProps, {}); + }; + virtual State::Shared createInitialState( ShadowNodeFragment const &fragment, ShadowNodeFamily::Shared const &family) const override { diff --git a/ReactCommon/fabric/core/events/BatchedEventQueue.cpp b/ReactCommon/fabric/core/events/BatchedEventQueue.cpp index 49758f22bb18de..baf25201294dd4 100644 --- a/ReactCommon/fabric/core/events/BatchedEventQueue.cpp +++ b/ReactCommon/fabric/core/events/BatchedEventQueue.cpp @@ -6,6 +6,7 @@ */ #include "BatchedEventQueue.h" +#include namespace facebook { namespace react { @@ -16,5 +17,23 @@ void BatchedEventQueue::onEnqueue() const { eventBeat_->request(); } +void BatchedEventQueue::enqueueUniqueEvent(const RawEvent &rawEvent) const { + { + std::lock_guard lock(queueMutex_); + auto const position = std::find_if( + eventQueue_.begin(), eventQueue_.end(), [&rawEvent](auto const &event) { + return event.type == rawEvent.type && + event.eventTarget == rawEvent.eventTarget; + }); + if (position != eventQueue_.end()) { + eventQueue_.erase(position); + } + + eventQueue_.push_back(rawEvent); + } + + onEnqueue(); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/events/BatchedEventQueue.h b/ReactCommon/fabric/core/events/BatchedEventQueue.h index f241f24a176d27..84bcb0ff39b8ab 100644 --- a/ReactCommon/fabric/core/events/BatchedEventQueue.h +++ b/ReactCommon/fabric/core/events/BatchedEventQueue.h @@ -21,6 +21,14 @@ class BatchedEventQueue final : public EventQueue { using EventQueue::EventQueue; void onEnqueue() const override; + + /* + * Enqueues and (probably later) dispatch a given event. + * Deletes previous RawEvent of the same type and with same target + * from the queue. + * Can be called on any thread. + */ + void enqueueUniqueEvent(const RawEvent &rawEvent) const; }; } // namespace react diff --git a/ReactCommon/fabric/core/events/EventDispatcher.cpp b/ReactCommon/fabric/core/events/EventDispatcher.cpp index dd08e2330ca305..5ccd35e569122e 100644 --- a/ReactCommon/fabric/core/events/EventDispatcher.cpp +++ b/ReactCommon/fabric/core/events/EventDispatcher.cpp @@ -21,27 +21,23 @@ EventDispatcher::EventDispatcher( StatePipe const &statePipe, EventBeat::Factory const &synchonousEventBeatFactory, EventBeat::Factory const &asynchonousEventBeatFactory, - EventBeat::SharedOwnerBox const &ownerBox) { - // Synchronous/Unbatched - eventQueues_[(int)EventPriority::SynchronousUnbatched] = - std::make_unique( - eventPipe, statePipe, synchonousEventBeatFactory(ownerBox)); - - // Synchronous/Batched - eventQueues_[(int)EventPriority::SynchronousBatched] = - std::make_unique( - eventPipe, statePipe, synchonousEventBeatFactory(ownerBox)); - - // Asynchronous/Unbatched - eventQueues_[(int)EventPriority::AsynchronousUnbatched] = - std::make_unique( - eventPipe, statePipe, asynchonousEventBeatFactory(ownerBox)); - - // Asynchronous/Batched - eventQueues_[(int)EventPriority::AsynchronousBatched] = - std::make_unique( - eventPipe, statePipe, asynchonousEventBeatFactory(ownerBox)); -} + EventBeat::SharedOwnerBox const &ownerBox) + : synchronousUnbatchedQueue_(std::make_unique( + eventPipe, + statePipe, + synchonousEventBeatFactory(ownerBox))), + synchronousBatchedQueue_(std::make_unique( + eventPipe, + statePipe, + synchonousEventBeatFactory(ownerBox))), + asynchronousUnbatchedQueue_(std::make_unique( + eventPipe, + statePipe, + asynchonousEventBeatFactory(ownerBox))), + asynchronousBatchedQueue_(std::make_unique( + eventPipe, + statePipe, + asynchonousEventBeatFactory(ownerBox))) {} void EventDispatcher::dispatchEvent( RawEvent const &rawEvent, @@ -55,8 +51,21 @@ void EventDispatcher::dispatchStateUpdate( getEventQueue(priority).enqueueStateUpdate(std::move(stateUpdate)); } +void EventDispatcher::dispatchUniqueEvent(RawEvent const &rawEvent) const { + asynchronousBatchedQueue_->enqueueUniqueEvent(rawEvent); +} + const EventQueue &EventDispatcher::getEventQueue(EventPriority priority) const { - return *eventQueues_[(int)priority]; + switch (priority) { + case EventPriority::SynchronousUnbatched: + return *synchronousUnbatchedQueue_; + case EventPriority::SynchronousBatched: + return *synchronousBatchedQueue_; + case EventPriority::AsynchronousUnbatched: + return *asynchronousUnbatchedQueue_; + case EventPriority::AsynchronousBatched: + return *asynchronousBatchedQueue_; + } } } // namespace react diff --git a/ReactCommon/fabric/core/events/EventDispatcher.h b/ReactCommon/fabric/core/events/EventDispatcher.h index 47a1d826ad3580..f9f161bd50e75d 100644 --- a/ReactCommon/fabric/core/events/EventDispatcher.h +++ b/ReactCommon/fabric/core/events/EventDispatcher.h @@ -10,12 +10,13 @@ #include #include +#include #include #include #include -#include #include #include +#include namespace facebook { namespace react { @@ -43,6 +44,13 @@ class EventDispatcher { */ void dispatchEvent(RawEvent const &rawEvent, EventPriority priority) const; + /* + * Dispatches a raw event with asynchronous batched priority. Before the + * dispatch we make sure that no other RawEvent of same type and same target + * is on the queue. + */ + void dispatchUniqueEvent(RawEvent const &rawEvent) const; + /* * Dispatches a state update with given priority. */ @@ -52,7 +60,10 @@ class EventDispatcher { private: EventQueue const &getEventQueue(EventPriority priority) const; - std::array, 4> eventQueues_; + std::unique_ptr synchronousUnbatchedQueue_; + std::unique_ptr synchronousBatchedQueue_; + std::unique_ptr asynchronousUnbatchedQueue_; + std::unique_ptr asynchronousBatchedQueue_; }; } // namespace react diff --git a/ReactCommon/fabric/core/events/EventEmitter.cpp b/ReactCommon/fabric/core/events/EventEmitter.cpp index 368bdad8d31eb3..007911fcc593c2 100644 --- a/ReactCommon/fabric/core/events/EventEmitter.cpp +++ b/ReactCommon/fabric/core/events/EventEmitter.cpp @@ -77,6 +77,20 @@ void EventEmitter::dispatchEvent( priority); } +void EventEmitter::dispatchUniqueEvent( + const std::string &type, + const ValueFactory &payloadFactory) const { + SystraceSection s("EventEmitter::dispatchUniqueEvent"); + + auto eventDispatcher = eventDispatcher_.lock(); + if (!eventDispatcher) { + return; + } + + eventDispatcher->dispatchUniqueEvent( + RawEvent(normalizeEventType(type), payloadFactory, eventTarget_)); +} + void EventEmitter::setEnabled(bool enabled) const { enableCounter_ += enabled ? 1 : -1; diff --git a/ReactCommon/fabric/core/events/EventEmitter.h b/ReactCommon/fabric/core/events/EventEmitter.h index 91db82ed3a611f..14475e01a019d3 100644 --- a/ReactCommon/fabric/core/events/EventEmitter.h +++ b/ReactCommon/fabric/core/events/EventEmitter.h @@ -78,6 +78,11 @@ class EventEmitter { const folly::dynamic &payload, const EventPriority &priority = EventPriority::AsynchronousBatched) const; + void dispatchUniqueEvent( + const std::string &type, + const ValueFactory &payloadFactory = + EventEmitter::defaultPayloadFactory()) const; + private: void toggleEventTargetOwnership_() const; diff --git a/ReactCommon/fabric/core/events/EventQueue.cpp b/ReactCommon/fabric/core/events/EventQueue.cpp index 1d1aa9e6ea0165..2861d7a14f05ec 100644 --- a/ReactCommon/fabric/core/events/EventQueue.cpp +++ b/ReactCommon/fabric/core/events/EventQueue.cpp @@ -96,7 +96,7 @@ void EventQueue::flushStateUpdates() const { { std::lock_guard lock(queueMutex_); - if (stateUpdateQueue_.size() == 0) { + if (stateUpdateQueue_.empty()) { return; } diff --git a/ReactCommon/fabric/core/events/RawEvent.h b/ReactCommon/fabric/core/events/RawEvent.h index e3985cef83b209..8ac0800b586d14 100644 --- a/ReactCommon/fabric/core/events/RawEvent.h +++ b/ReactCommon/fabric/core/events/RawEvent.h @@ -26,9 +26,9 @@ class RawEvent { ValueFactory payloadFactory, SharedEventTarget eventTarget); - const std::string type; - const ValueFactory payloadFactory; - const SharedEventTarget eventTarget; + std::string type; + ValueFactory payloadFactory; + SharedEventTarget eventTarget; }; } // namespace react diff --git a/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp b/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp index 4ae44ed720e3cc..c6f391eccef261 100644 --- a/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp +++ b/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp @@ -17,54 +17,95 @@ namespace facebook { namespace react { -/* - * `shadowNode` might not be the newest revision of `ShadowNodeFamily`. - * This function looks at `parentNode`'s children and finds one that belongs - * to the same family as `shadowNode`. - */ -static ShadowNode const *findNewestChildInParent( - ShadowNode const &parentNode, - ShadowNode const &shadowNode) { - for (auto const &child : parentNode.getChildren()) { - if (ShadowNode::sameFamily(*child, shadowNode)) { - return child.get(); +LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( + ShadowNodeFamily const &descendantNodeFamily, + LayoutableShadowNode const &ancestorNode, + LayoutInspectingPolicy policy) { + if (&descendantNodeFamily == &ancestorNode.getFamily()) { + // Layout metrics of a node computed relatively to the same node are equal + // to `transform`-ed layout metrics of the node with zero `origin`. + auto layoutMetrics = ancestorNode.getLayoutMetrics(); + if (policy.includeTransform) { + layoutMetrics.frame = layoutMetrics.frame * ancestorNode.getTransform(); } + layoutMetrics.frame.origin = {0, 0}; + return layoutMetrics; } - return nullptr; -} -static LayoutMetrics calculateOffsetForLayoutMetrics( - LayoutMetrics layoutMetrics, - ShadowNode::AncestorList const &ancestors, - LayoutableShadowNode::LayoutInspectingPolicy const &policy) { - // `AncestorList` starts from the given ancestor node and ends with the parent - // node. We iterate from parent node (reverse iteration) and stop before the - // given ancestor (rend() - 1). - for (auto it = ancestors.rbegin(); it != ancestors.rend() - 1; ++it) { - auto ¤tShadowNode = it->first.get(); - - if (currentShadowNode.getTraits().check( - ShadowNodeTraits::Trait::RootNodeKind)) { + auto ancestors = descendantNodeFamily.getAncestors(ancestorNode); + + if (ancestors.size() == 0) { + // Specified nodes do not form an ancestor-descender relationship + // in the same tree. Aborting. + return EmptyLayoutMetrics; + } + + // Step 1. + // Creating a list of nodes that form a chain from the descender node to + // ancestor node inclusively. + auto shadowNodeList = better::small_vector{}; + + // Finding the measured node. + // The last element in the `AncestorList` is a pair of a parent of the node + // and an index of this node in the parent's children list. + auto &pair = ancestors.at(ancestors.size() - 1); + auto descendantNode = pair.first.get().getChildren().at(pair.second).get(); + + // Putting the node inside the list. + // Even if this is a node with a `RootNodeKind` trait, we don't treat it as + // root because we measure it from an outside tree perspective. + shadowNodeList.push_back(descendantNode); + + for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) { + auto &shadowNode = it->first.get(); + + shadowNodeList.push_back(&shadowNode); + + if (shadowNode.getTraits().check(ShadowNodeTraits::Trait::RootNodeKind)) { + // If this is a node with a `RootNodeKind` trait, we need to stop right + // there. break; } + } + + // Step 2. + // Computing the initial size of the measured node. + auto descendantLayoutableNode = + traitCast(descendantNode); - auto layoutableCurrentShadowNode = - dynamic_cast(¤tShadowNode); + if (!descendantLayoutableNode) { + return EmptyLayoutMetrics; + } - if (!layoutableCurrentShadowNode) { + auto layoutMetrics = descendantLayoutableNode->getLayoutMetrics(); + auto &resultFrame = layoutMetrics.frame; + resultFrame.origin = {0, 0}; + + // Step 3. + // Iterating on a list of nodes computing compound offset. + auto size = shadowNodeList.size(); + for (int i = 0; i < size; i++) { + auto currentShadowNode = + traitCast(shadowNodeList.at(i)); + + if (!currentShadowNode) { return EmptyLayoutMetrics; } - auto frame = layoutableCurrentShadowNode->getLayoutMetrics().frame; + auto currentFrame = currentShadowNode->getLayoutMetrics().frame; + if (i == size - 1) { + // If it's the last element, its origin is irrelevant. + currentFrame.origin = {0, 0}; + } if (policy.includeTransform) { - layoutMetrics.frame.size = layoutMetrics.frame.size * - layoutableCurrentShadowNode->getTransform(); - frame = frame * layoutableCurrentShadowNode->getTransform(); + resultFrame.size = resultFrame.size * currentShadowNode->getTransform(); + currentFrame = currentFrame * currentShadowNode->getTransform(); } - layoutMetrics.frame.origin += frame.origin; + resultFrame.origin += currentFrame.origin; } + return layoutMetrics; } @@ -109,37 +150,8 @@ Transform LayoutableShadowNode::getTransform() const { LayoutMetrics LayoutableShadowNode::getRelativeLayoutMetrics( LayoutableShadowNode const &ancestorLayoutableShadowNode, LayoutInspectingPolicy policy) const { - auto &ancestorShadowNode = - dynamic_cast(ancestorLayoutableShadowNode); - auto &shadowNode = dynamic_cast(*this); - - if (ShadowNode::sameFamily(shadowNode, ancestorShadowNode)) { - auto layoutMetrics = getLayoutMetrics(); - layoutMetrics.frame.origin = {0, 0}; - return layoutMetrics; - } - - auto ancestors = shadowNode.getFamily().getAncestors(ancestorShadowNode); - - if (ancestors.size() == 0) { - return EmptyLayoutMetrics; - } - - auto newestChild = - findNewestChildInParent(ancestors.rbegin()->first.get(), shadowNode); - - if (!newestChild) { - return EmptyLayoutMetrics; - } - - auto layoutableNewestChild = - dynamic_cast(newestChild); - auto layoutMetrics = layoutableNewestChild->getLayoutMetrics(); - if (policy.includeTransform) { - layoutMetrics.frame = - layoutMetrics.frame * layoutableNewestChild->getTransform(); - } - return calculateOffsetForLayoutMetrics(layoutMetrics, ancestors, policy); + return computeRelativeLayoutMetrics( + getFamily(), ancestorLayoutableShadowNode, policy); } LayoutableShadowNode::UnsharedList diff --git a/ReactCommon/fabric/core/layout/LayoutableShadowNode.h b/ReactCommon/fabric/core/layout/LayoutableShadowNode.h index 78601fa5e221c0..d94566390c443b 100644 --- a/ReactCommon/fabric/core/layout/LayoutableShadowNode.h +++ b/ReactCommon/fabric/core/layout/LayoutableShadowNode.h @@ -51,6 +51,17 @@ class LayoutableShadowNode : public ShadowNode { using UnsharedList = better:: small_vector; + /* + * Returns layout metrics of a node represented as `descendantNodeFamily` + * computed relatively to given `ancestorNode`. Returns `EmptyLayoutMetrics` + * if the nodes don't form an ancestor-descender relationship in the same + * tree. + */ + static LayoutMetrics computeRelativeLayoutMetrics( + ShadowNodeFamily const &descendantNodeFamily, + LayoutableShadowNode const &ancestorNode, + LayoutInspectingPolicy policy); + /* * Performs layout of the tree starting from this node. Usually is being * called on the root node. @@ -99,6 +110,14 @@ class LayoutableShadowNode : public ShadowNode { */ virtual Transform getTransform() const; + /* + * Returns layout metrics relatively to the given ancestor node. + * Uses `computeRelativeLayoutMetrics()` under the hood. + */ + LayoutMetrics getRelativeLayoutMetrics( + ShadowNodeFamily const &descendantNodeFamily, + LayoutInspectingPolicy policy) const; + /* * Returns layout metrics relatively to the given ancestor node. */ diff --git a/ReactCommon/fabric/core/primitives/RawValue.h b/ReactCommon/fabric/core/primitives/RawValue.h index 67f6ac05dcc252..75c2e9527a7cbc 100644 --- a/ReactCommon/fabric/core/primitives/RawValue.h +++ b/ReactCommon/fabric/core/primitives/RawValue.h @@ -64,6 +64,7 @@ class RawValue { private: friend class RawProps; friend class RawPropsParser; + friend class UIManagerBinding; /* * Arbitrary constructors are private only for RawProps and internal usage. @@ -73,9 +74,9 @@ class RawValue { RawValue(folly::dynamic &&dynamic) noexcept : dynamic_(std::move(dynamic)){}; /* - * Copy constructor and copy assignment operator are private and only for - * internal use. Basically, it's implementation details. Other particular - * implementations of the `RawValue` interface may not have them. + * Copy constructor and copy assignment operator would be private and only for + * internal use, but it's needed for user-code that does `auto val = + * (better::map)rawVal;` */ RawValue(RawValue const &other) noexcept : dynamic_(other.dynamic_) {} diff --git a/ReactCommon/fabric/core/shadownode/Props.h b/ReactCommon/fabric/core/shadownode/Props.h index 340041448c790a..6e0f46b3785ee2 100644 --- a/ReactCommon/fabric/core/shadownode/Props.h +++ b/ReactCommon/fabric/core/shadownode/Props.h @@ -43,7 +43,7 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { int const revision{0}; #ifdef ANDROID - folly::dynamic const rawProps = folly::dynamic::object(); + folly::dynamic rawProps = folly::dynamic::object(); #endif }; diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp index f54fedcf22b073..9f3618ddf466f3 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp @@ -254,7 +254,7 @@ ShadowNode::Unshared ShadowNode::cloneTree( callback) const { auto ancestors = shadowNodeFamily.getAncestors(*this); - if (ancestors.size() == 0) { + if (ancestors.empty()) { return ShadowNode::Unshared{nullptr}; } diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.h b/ReactCommon/fabric/core/shadownode/ShadowNode.h index 468670698f7778..30710ec0e468e4 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.h @@ -90,6 +90,12 @@ class ShadowNode : public Sealable, public DebugStringConvertible { const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment); + /* + * Not copyable. + */ + ShadowNode(ShadowNode const &shadowNode) noexcept = delete; + ShadowNode &operator=(ShadowNode const &other) noexcept = delete; + virtual ~ShadowNode() = default; /* diff --git a/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp b/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp index 9c13ae700f13d0..7d59e94b4534af 100644 --- a/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp +++ b/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp @@ -113,6 +113,8 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetrics) { // A is a parent to B, A has origin {10, 10}, B has origin {10, 10}. // B's relative origin to A should be {10, 10}. // D19447900 has more about the issue. + EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 200); EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 10); EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 20); } @@ -131,9 +133,10 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedNode) { EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 35); EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 70); - EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 50); EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 100); + + nodeAA_->_transform = Transform::Identity(); } TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedParent) { @@ -159,6 +162,8 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedParent) { EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 25); EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 25); + + nodeAAA_->_transform = Transform::Identity(); } TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameNode) { @@ -175,6 +180,23 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameNode) { EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 200); } +TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameTransformedNode) { + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {10, 20}; + layoutMetrics.frame.size = {100, 200}; + nodeA_->setLayoutMetrics(layoutMetrics); + nodeA_->_transform = Transform::Scale(2, 2, 1); + + auto relativeLayoutMetrics = nodeA_->getRelativeLayoutMetrics(*nodeA_, {}); + + EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 0); + EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 0); + EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 200); + EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 400); + + nodeA_->_transform = Transform::Identity(); +} + TEST_F(LayoutableShadowNodeTest, relativeLayourMetricsOnClonedNode) { // B is cloned and mutated. auto nodeBRevision2 = std::static_pointer_cast( @@ -192,15 +214,21 @@ TEST_F(LayoutableShadowNodeTest, relativeLayourMetricsOnClonedNode) { EXPECT_EQ(newRelativeLayoutMetrics.frame.size.height, 600); } -TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameNode2) { +TEST_F( + LayoutableShadowNodeTest, + relativeLayoutMetricsOnNodesCrossingRootKindNode) { auto layoutMetrics = EmptyLayoutMetrics; nodeA_->setLayoutMetrics(layoutMetrics); layoutMetrics.frame.origin = {10, 10}; + // nodeAA_ is a RootKindNode. Please check + // ShadowNodeTraits::Traits::RootNodeKind for details. nodeAA_->setLayoutMetrics(layoutMetrics); nodeAAA_->setLayoutMetrics(layoutMetrics); auto relativeLayoutMetrics = nodeAAA_->getRelativeLayoutMetrics(*nodeA_, {}); + // relativeLayoutMetrics do not include offsset of nodeAA_ because it is a + // RootKindNode. EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 10); EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 10); } diff --git a/ReactCommon/fabric/debug/BUCK b/ReactCommon/fabric/debug/BUCK index 9971b09cb503e3..af8ed5594dbc08 100644 --- a/ReactCommon/fabric/debug/BUCK +++ b/ReactCommon/fabric/debug/BUCK @@ -39,11 +39,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/element/BUCK b/ReactCommon/fabric/element/BUCK index f7fce793547bdd..c5fc20db81a342 100644 --- a/ReactCommon/fabric/element/BUCK +++ b/ReactCommon/fabric/element/BUCK @@ -38,9 +38,9 @@ rn_xplat_cxx_library( "-Wall", ], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/graphics/BUCK b/ReactCommon/fabric/graphics/BUCK index bcedde153577c2..a495bd817ac30c 100644 --- a/ReactCommon/fabric/graphics/BUCK +++ b/ReactCommon/fabric/graphics/BUCK @@ -56,14 +56,12 @@ rn_xplat_cxx_library( ], prefix = "react/graphics", ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( [ "platform/cxx/**/*.cpp", ], ), fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, ios_deps = [ @@ -87,6 +85,7 @@ rn_xplat_cxx_library( "platform/ios/**/*.mm", ], ), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/graphics/Quaternion.h b/ReactCommon/fabric/graphics/Quaternion.h new file mode 100644 index 00000000000000..4233a2c6cb4409 --- /dev/null +++ b/ReactCommon/fabric/graphics/Quaternion.h @@ -0,0 +1,208 @@ +/* + * Portions Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +// The following is a modified, stripped-down version of the Quaternion class +// by Frank Astier. Copyright notice below. +// The original has many, many more features, and has been stripped down +// to support the exact data-structures and use-cases we need for React Native. + +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Frank Astier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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 NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +namespace facebook { +namespace react { + +template +class Quaternion { + public: + /** + * Copy constructor. + */ + Quaternion(const Quaternion &y) : a_(y.a_), b_(y.b_), c_(y.c_), d_(y.d_) {} + + Quaternion(T a, T b, T c, T d) : a_(a), b_(b), c_(c), d_(d) {} + + static Quaternion fromRotationMatrix(std::array const &rm) { + T t = rm[0 * 4 + 0] + rm[1 * 4 + 1] + rm[2 * 4 + 2]; + if (t > 0) { + T s = (T)0.5 / std::sqrt(t + 1); + return {(T)0.25 / s, + (rm[2 * 4 + 1] - rm[1 * 4 + 2]) * s, + (rm[0 * 4 + 2] - rm[2 * 4 + 0]) * s, + (rm[1 * 4 + 0] - rm[0 * 4 + 1]) * s}; + } else if (rm[0 * 4 + 0] > rm[1 * 4 + 1] && rm[0 * 4 + 0] > rm[2 * 4 + 2]) { + T s = (T)2.0 * + std::sqrt( + 1.0 + rm[0 * 4 + 0] - rm[1 * 4 + 1] - rm[2 * 4 + 2]); // S=4*qx + return {(rm[2 * 4 + 1] - rm[1 * 4 + 2]) / s, + (T)0.25 * s, + (rm[0 * 4 + 1] + rm[1 * 4 + 0]) / s, + (rm[0 * 4 + 2] + rm[2 * 4 + 0]) / s}; + } else if (rm[1 * 4 + 1] > rm[2 * 4 + 2]) { + T s = (T)2.0 * + std::sqrt( + 1.0 + rm[1 * 4 + 1] - rm[0 * 4 + 0] - rm[2 * 4 + 2]); // S=4*qy + return {(rm[0 * 4 + 2] - rm[2 * 4 + 0]) / s, + (rm[0 * 4 + 1] + rm[1 * 4 + 0]) / s, + (T)0.25 * s, + (rm[1 * 4 + 2] + rm[2 * 4 + 1]) / s}; + } else { + T s = (T)2.0 * + std::sqrt( + 1.0 + rm[2 * 4 + 2] - rm[0 * 4 + 0] - rm[1 * 4 + 1]); // S=4*qz + return {(rm[1 * 4 + 0] - rm[0 * 4 + 1]) / s, + (rm[0 * 4 + 2] + rm[2 * 4 + 0]) / s, + (rm[1 * 4 + 2] + rm[2 * 4 + 1]) / s, + (T)0.25 * s}; + } + } + + /** + * Returns a 3D, 4x4 rotation matrix. + * This is the "homogeneous" expression to convert to a rotation matrix, + * which works even when the Quaternion is not a unit Quaternion. + */ + inline std::array toRotationMatrix4x4() { + T a2 = a_ * a_, b2 = b_ * b_, c2 = c_ * c_, d2 = d_ * d_; + T ab = a_ * b_, ac = a_ * c_, ad = a_ * d_; + T bc = b_ * c_, bd = b_ * d_; + T cd = c_ * d_; + return {a2 + b2 - c2 - d2, + 2 * (bc - ad), + 2 * (bd + ac), + 0, + 2 * (bc + ad), + a2 - b2 + c2 - d2, + 2 * (cd - ab), + 0, + 2 * (bd - ac), + 2 * (cd + ab), + a2 - b2 - c2 + d2, + 0, + 0, + 0, + 0, + 1}; + } + + inline Quaternion normalize() const { + assert(abs() > 0); // or this is not normalizable + T factor = abs(); + return *this / (factor != 0 ? factor : 1); + } + + inline T dot(const Quaternion &other) { + return a_ * other.a_ + b_ * other.b_ + c_ * other.c_ + d_ * other.d_; + } + + /** + * The square of the norm of the Quaternion. + * (The square is sometimes useful, and it avoids paying for a sqrt). + */ + inline T norm_squared() const { + return a_ * a_ + b_ * b_ + c_ * c_ + d_ * d_; + } + + /** + * The norm of the Quaternion (the l2 norm). + */ + inline T abs() const { + return std::sqrt(norm_squared()); + } + + inline Quaternion operator/=(T y) { + a_ /= y; + b_ /= y; + c_ /= y; + d_ /= y; + return *this; + } + + inline Quaternion operator*=(T y) { + a_ *= y; + b_ *= y; + c_ *= y; + d_ *= y; + return *this; + } + + inline Quaternion operator+=(Quaternion const &other) { + a_ += other.a_; + b_ += other.b_; + c_ += other.c_; + d_ += other.d_; + return *this; + } + + inline Quaternion operator-=(Quaternion const &other) { + a_ -= other.a_; + b_ -= other.b_; + c_ -= other.c_; + d_ -= other.d_; + return *this; + } + + private: + T a_; // AKA w, qw + T b_; // AKA x, qx + T c_; // AKA y, qy + T d_; // AKA z, qz +}; + +template +inline Quaternion operator/(Quaternion const &lhs, T rhs) { + return Quaternion(lhs) /= rhs; +} + +template +inline Quaternion operator*(Quaternion const &lhs, T rhs) { + return Quaternion(lhs) *= rhs; +} + +template +inline Quaternion operator+( + Quaternion const &lhs, + Quaternion const &rhs) { + return Quaternion(lhs) += rhs; +} + +template +inline Quaternion operator-( + Quaternion const &lhs, + Quaternion const &rhs) { + return Quaternion(lhs) -= rhs; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/graphics/Transform.cpp b/ReactCommon/fabric/graphics/Transform.cpp index 7293e03312b855..f55721c3fc6be8 100644 --- a/ReactCommon/fabric/graphics/Transform.cpp +++ b/ReactCommon/fabric/graphics/Transform.cpp @@ -7,18 +7,34 @@ #include "Transform.h" +#include #include +#include + namespace facebook { namespace react { +#ifdef RN_DEBUG_STRING_CONVERTIBLE +void Transform::print(Transform const &t, std::string prefix) { + LOG(ERROR) << prefix << "[ " << t.matrix[0] << " " << t.matrix[1] << " " + << t.matrix[2] << " " << t.matrix[3] << " ]"; + LOG(ERROR) << prefix << "[ " << t.matrix[4] << " " << t.matrix[5] << " " + << t.matrix[6] << " " << t.matrix[7] << " ]"; + LOG(ERROR) << prefix << "[ " << t.matrix[8] << " " << t.matrix[9] << " " + << t.matrix[10] << " " << t.matrix[11] << " ]"; + LOG(ERROR) << prefix << "[ " << t.matrix[12] << " " << t.matrix[13] << " " + << t.matrix[14] << " " << t.matrix[15] << " ]"; +} +#endif + Transform Transform::Identity() { return {}; } Transform Transform::Perspective(Float perspective) { auto transform = Transform{}; - transform.matrix[11] = -1.0 / perspective; + transform.matrix[11] = -1 / perspective; return transform; } @@ -86,6 +102,132 @@ Transform Transform::Rotate(Float x, Float y, Float z) { return transform; } +Transform::SRT Transform::ExtractSRT(Transform const &t) { + // First we need to extract translation, rotation, and scale from both + // matrices, in that order. Matrices must be in this form: [a b c d] [e f g h] + // [i j k l] + // [0 0 0 1] + // We also assume that all scale factors are non-negative, because in + assert( + t.matrix[12] == 0 && t.matrix[13] == 0 && t.matrix[14] == 0 && + t.matrix[15] == 1 && "Last row of matrix must be [0,0,0,1]"); + + // lhs: + // Translation: extract the values from the rightmost column + Float translationX = t.matrix[3]; + Float translationY = t.matrix[7]; + Float translationZ = t.matrix[11]; + + // Scale: the length of the first three column vectors + // TODO: do we need to do anything special for negative scale factors? + // the last element is a uniform scale factor + Float scaleX = t.matrix[15] * + sqrt(pow(t.matrix[0], 2) + pow(t.matrix[4], 2) + + pow(t.matrix[8], 2)); // sqrt(a^2 + e^2 + i^2) + Float scaleY = t.matrix[15] * + sqrt(pow(t.matrix[1], 2) + pow(t.matrix[5], 2) + + pow(t.matrix[9], 2)); // sqrt(b^2 + f^2 + j^2) + Float scaleZ = t.matrix[15] * + sqrt(pow(t.matrix[2], 2) + pow(t.matrix[6], 2) + + pow(t.matrix[10], 2)); // sqrt(c^2 + g^2 + k^2) + + Float rScaleFactorX = scaleX == 0 ? 1 : scaleX; + Float rScaleFactorY = scaleY == 0 ? 1 : scaleY; + Float rScaleFactorZ = scaleZ == 0 ? 1 : scaleZ; + + // Construct a rotation matrix and convert that to quaternions + auto rotationMatrix = std::array{t.matrix[0] / rScaleFactorX, + t.matrix[1] / rScaleFactorY, + t.matrix[2] / rScaleFactorZ, + 0, + t.matrix[4] / rScaleFactorX, + t.matrix[5] / rScaleFactorY, + t.matrix[6] / rScaleFactorZ, + 0, + t.matrix[8] / rScaleFactorX, + t.matrix[9] / rScaleFactorY, + t.matrix[10] / rScaleFactorZ, + 0, + 0, + 0, + 0, + 1}; + + Quaternion q = + Quaternion::fromRotationMatrix(rotationMatrix).normalize(); + + return Transform::SRT{ + translationX, translationY, translationZ, scaleX, scaleY, scaleZ, q}; +} + +Transform Transform::Interpolate( + float animationProgress, + Transform const &lhs, + Transform const &rhs) { + // Extract SRT for both sides + // This is extracted in the form: X,Y,Z coordinates for translations; X,Y,Z + // coordinates for scale; and a quaternion for rotation. + auto lhsSRT = ExtractSRT(lhs); + auto rhsSRT = ExtractSRT(rhs); + + // Interpolate translation and scale terms linearly (LERP) + Float translateX = + (lhsSRT.translationX + + (rhsSRT.translationX - lhsSRT.translationX) * animationProgress); + Float translateY = + (lhsSRT.translationY + + (rhsSRT.translationY - lhsSRT.translationY) * animationProgress); + Float translateZ = + (lhsSRT.translationZ + + (rhsSRT.translationZ - lhsSRT.translationZ) * animationProgress); + Float scaleX = + (lhsSRT.scaleX + (rhsSRT.scaleX - lhsSRT.scaleX) * animationProgress); + Float scaleY = + (lhsSRT.scaleY + (rhsSRT.scaleY - lhsSRT.scaleY) * animationProgress); + Float scaleZ = + (lhsSRT.scaleZ + (rhsSRT.scaleZ - lhsSRT.scaleZ) * animationProgress); + + // Use the quaternion vectors to produce an interpolated rotation via SLERP + // dot: cos of the angle between the two quaternion vectors + Quaternion q1 = lhsSRT.rotation; + Quaternion q2 = rhsSRT.rotation; + Float dot = q1.dot(q2); + // Clamp dot between -1 and 1 + dot = (dot < -1 ? -1 : (dot > 1 ? 1 : dot)); + // There are two ways of performing an identical slerp: q1 and -q1. + // If the dot-product is negative, we can multiply q1 by -1 and our animation + // will take the "short way" around instead of the "long way". + if (dot < 0) { + q1 = q1 * (Float)-1; + dot = dot * -1; + } + // Interpolated angle + Float theta = acosf(dot) * animationProgress; + + Transform rotation = Transform::Identity(); + + // Compute orthonormal basis + Quaternion orthonormalBasis = (q2 - q1 * dot); + + if (orthonormalBasis.abs() > 0) { + Quaternion orthonormalBasisNormalized = orthonormalBasis.normalize(); + + // Compute orthonormal basis + // Final quaternion result - slerp! + Quaternion resultingRotationVec = + (q1 * (Float)cos(theta) + + orthonormalBasisNormalized * (Float)sin(theta)) + .normalize(); + + // Convert quaternion to matrix + rotation.matrix = resultingRotationVec.toRotationMatrix4x4(); + } + + // Compose matrices and return + return (Scale(scaleX, scaleY, scaleZ) * rotation) * + Translate(translateX, translateY, translateZ); +} + bool Transform::operator==(Transform const &rhs) const { for (auto i = 0; i < 16; i++) { if (matrix[i] != rhs.matrix[i]) { diff --git a/ReactCommon/fabric/graphics/Transform.h b/ReactCommon/fabric/graphics/Transform.h index 49d8c095fd3fcd..357e2863d7df93 100644 --- a/ReactCommon/fabric/graphics/Transform.h +++ b/ReactCommon/fabric/graphics/Transform.h @@ -12,17 +12,41 @@ #include #include #include +#include + +#ifdef ANDROID +#include +#endif namespace facebook { namespace react { +struct ScaleRotationTranslation { + Float translationX; + Float translationY; + Float translationZ; + Float scaleX; + Float scaleY; + Float scaleZ; + Quaternion rotation; +}; + /* * Defines transform matrix to apply affine transformations. */ struct Transform { + using SRT = ScaleRotationTranslation; + std::array matrix{ {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}; + /** + * For debugging only. Prints out the matrix. + */ +#ifdef RN_DEBUG_STRING_CONVERTIBLE + static void print(Transform const &t, std::string prefix); +#endif + /* * Returns the identity transform (`[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]`). */ @@ -56,6 +80,41 @@ struct Transform { static Transform RotateZ(Float angle); static Transform Rotate(Float angleX, Float angleY, Float angleZ); + /** + * Extract SRT (scale, rotation, transformation) from a Transform matrix. + * + * CAVEATS: + * 1. The input matrix must not have Skew applied. + * 2. Scaling factors must be non-negative. Scaling by a negative factor is + * equivalent to a rotation, and though it is possible to detect if 1 or + * 3 of the scale signs are flipped (but not two), it is not possible + * to detect WHICH of the scales are flipped. Thus, any animation + * that involves a negative scale factor will not crash but will + * interpolate over nonsensical values. + * 3. Another caveat is that if the animation interpolates TO a 90º + * rotation in the X, Y, or Z axis, the View will appear to suddenly + * explode in size. Interpolating THROUGH 90º is fine as long as you don't end + * up at 90º or close to it (89.99). The same is true for 0±90 and 360n+90, + * etc. + */ + static SRT ExtractSRT(Transform const &transform); + + /** + * Perform an interpolation between lhs and rhs, given progress. + * This first decomposes the matrices into translation, scale, and rotation, + * performs slerp between the two rotations, and a linear interpolation + * of scale and translation. + * + * @param progress + * @param lhs + * @param rhs + * @return + */ + static Transform Interpolate( + float animationProgress, + Transform const &lhs, + Transform const &rhs); + /* * Equality operators. */ @@ -72,6 +131,31 @@ struct Transform { * Concatenates (multiplies) transform matrices. */ Transform operator*(Transform const &rhs) const; + + /** + * Convert to folly::dynamic. + */ +#ifdef ANDROID + operator folly::dynamic() const { + return folly::dynamic::array( + matrix[0], + matrix[1], + matrix[2], + matrix[3], + matrix[4], + matrix[5], + matrix[6], + matrix[7], + matrix[8], + matrix[9], + matrix[10], + matrix[11], + matrix[12], + matrix[13], + matrix[14], + matrix[15]); + } +#endif }; /* diff --git a/ReactCommon/fabric/imagemanager/BUCK b/ReactCommon/fabric/imagemanager/BUCK index ba638f1d1e8098..0e1749e9973541 100644 --- a/ReactCommon/fabric/imagemanager/BUCK +++ b/ReactCommon/fabric/imagemanager/BUCK @@ -45,14 +45,12 @@ rn_xplat_cxx_library( ], prefix = "", ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( [ "platform/cxx/**/*.cpp", ], ), fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags() + [ "-DLOG_TAG=\"ReactNative\"", "-DWITH_FBSYSTRACE=1", @@ -88,6 +86,7 @@ rn_xplat_cxx_library( "platform/ios/**/*.mm", ], ), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/mapbuffer/BUCK b/ReactCommon/fabric/mapbuffer/BUCK index 89acfd5f302c30..fd968ba4362693 100644 --- a/ReactCommon/fabric/mapbuffer/BUCK +++ b/ReactCommon/fabric/mapbuffer/BUCK @@ -30,8 +30,8 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID), preprocessor_flags = [ diff --git a/ReactCommon/fabric/mounting/BUCK b/ReactCommon/fabric/mounting/BUCK index afa937d70bd0cc..46531cf3e49ffa 100644 --- a/ReactCommon/fabric/mounting/BUCK +++ b/ReactCommon/fabric/mounting/BUCK @@ -38,11 +38,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/mounting/Differentiator.cpp b/ReactCommon/fabric/mounting/Differentiator.cpp index 6661d5424407b2..e1ac64cbf35dca 100644 --- a/ReactCommon/fabric/mounting/Differentiator.cpp +++ b/ReactCommon/fabric/mounting/Differentiator.cpp @@ -65,7 +65,7 @@ class TinyMap final { inline Iterator end() { // `back()` asserts on the vector being non-empty - if (vector_.size() == 0 || numErased_ == vector_.size()) { + if (vector_.empty() || numErased_ == vector_.size()) { return nullptr; } @@ -112,7 +112,7 @@ class TinyMap final { */ inline Iterator begin_() { // `front()` asserts on the vector being non-empty - if (vector_.size() == 0 || vector_.size() == numErased_) { + if (vector_.empty() || vector_.size() == numErased_) { return nullptr; } @@ -125,9 +125,8 @@ class TinyMap final { * vector. */ inline void cleanVector(bool forceClean = false) { - if ((numErased_ < (vector_.size() / 2) && !forceClean) || - vector_.size() == 0 || numErased_ == 0 || - numErased_ == erasedAtFront_) { + if ((numErased_ < (vector_.size() / 2) && !forceClean) || vector_.empty() || + numErased_ == 0 || numErased_ == erasedAtFront_) { return; } @@ -190,7 +189,9 @@ static void sliceChildShadowNodeViewPairsRecursively( for (auto const &sharedChildShadowNode : shadowNode.getChildren()) { auto &childShadowNode = *sharedChildShadowNode; auto shadowView = ShadowView(childShadowNode); + auto origin = layoutOffset; if (shadowView.layoutMetrics != EmptyLayoutMetrics) { + origin += shadowView.layoutMetrics.frame.origin; shadowView.layoutMetrics.frame.origin += layoutOffset; } @@ -204,7 +205,7 @@ static void sliceChildShadowNodeViewPairsRecursively( } sliceChildShadowNodeViewPairsRecursively( - pairList, shadowView.layoutMetrics.frame.origin, childShadowNode); + pairList, origin, childShadowNode); } } } @@ -254,190 +255,12 @@ static_assert( std::is_move_assignable::value, "`ShadowViewNodePair::List` must be `move assignable`."); -static void calculateShadowViewMutationsClassic( +static void calculateShadowViewMutations( ShadowViewMutation::List &mutations, ShadowView const &parentShadowView, ShadowViewNodePair::List &&oldChildPairs, ShadowViewNodePair::List &&newChildPairs) { - // This version of the algorithm is optimized for simplicity, - // not for performance or optimal result. - - if (oldChildPairs.size() == 0 && newChildPairs.size() == 0) { - return; - } - - // Sorting pairs based on `orderIndex` if needed. - reorderInPlaceIfNeeded(oldChildPairs); - reorderInPlaceIfNeeded(newChildPairs); - - auto index = int{0}; - - // Maps inserted node tags to pointers to them in `newChildPairs`. - auto insertedPairs = TinyMap{}; - - // Lists of mutations - auto createMutations = ShadowViewMutation::List{}; - auto deleteMutations = ShadowViewMutation::List{}; - auto insertMutations = ShadowViewMutation::List{}; - auto removeMutations = ShadowViewMutation::List{}; - auto updateMutations = ShadowViewMutation::List{}; - auto downwardMutations = ShadowViewMutation::List{}; - auto destructiveDownwardMutations = ShadowViewMutation::List{}; - - // Stage 1: Collecting `Update` mutations - for (index = 0; index < oldChildPairs.size() && index < newChildPairs.size(); - index++) { - auto const &oldChildPair = oldChildPairs[index]; - auto const &newChildPair = newChildPairs[index]; - - if (oldChildPair.shadowView.tag != newChildPair.shadowView.tag) { - // Totally different nodes, updating is impossible. - break; - } - - if (oldChildPair.shadowView != newChildPair.shadowView) { - updateMutations.push_back(ShadowViewMutation::UpdateMutation( - parentShadowView, - oldChildPair.shadowView, - newChildPair.shadowView, - index)); - } - - auto oldGrandChildPairs = - sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); - auto newGrandChildPairs = - sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); - calculateShadowViewMutationsClassic( - *(newGrandChildPairs.size() ? &downwardMutations - : &destructiveDownwardMutations), - oldChildPair.shadowView, - std::move(oldGrandChildPairs), - std::move(newGrandChildPairs)); - } - - int lastIndexAfterFirstStage = index; - - // Stage 2: Collecting `Insert` mutations - for (; index < newChildPairs.size(); index++) { - auto const &newChildPair = newChildPairs[index]; - - insertMutations.push_back(ShadowViewMutation::InsertMutation( - parentShadowView, newChildPair.shadowView, index)); - - insertedPairs.insert({newChildPair.shadowView.tag, &newChildPair}); - } - - // Stage 3: Collecting `Delete` and `Remove` mutations - for (index = lastIndexAfterFirstStage; index < oldChildPairs.size(); - index++) { - auto const &oldChildPair = oldChildPairs[index]; - - // Even if the old view was (re)inserted, we have to generate `remove` - // mutation. - removeMutations.push_back(ShadowViewMutation::RemoveMutation( - parentShadowView, oldChildPair.shadowView, index)); - - auto const it = insertedPairs.find(oldChildPair.shadowView.tag); - - if (it == insertedPairs.end()) { - // The old view was *not* (re)inserted. - // We have to generate `delete` mutation and apply the algorithm - // recursively. - deleteMutations.push_back( - ShadowViewMutation::DeleteMutation(oldChildPair.shadowView)); - - // We also have to call the algorithm recursively to clean up the entire - // subtree starting from the removed view. - calculateShadowViewMutationsClassic( - destructiveDownwardMutations, - oldChildPair.shadowView, - sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode), - {}); - } else { - // The old view *was* (re)inserted. - // We have to call the algorithm recursively if the inserted view - // is *not* the same as removed one. - auto const &newChildPair = *it->second; - - if (newChildPair != oldChildPair) { - auto oldGrandChildPairs = - sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); - auto newGrandChildPairs = - sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); - calculateShadowViewMutationsClassic( - *(newGrandChildPairs.size() ? &downwardMutations - : &destructiveDownwardMutations), - newChildPair.shadowView, - std::move(oldGrandChildPairs), - std::move(newGrandChildPairs)); - } - - // In any case we have to remove the view from `insertedPairs` as - // indication that the view was actually removed (which means that - // the view existed before), hence we don't have to generate - // `create` mutation. - insertedPairs.erase(it); - } - } - - // Stage 4: Collecting `Create` mutations - for (index = lastIndexAfterFirstStage; index < newChildPairs.size(); - index++) { - auto const &newChildPair = newChildPairs[index]; - - if (insertedPairs.find(newChildPair.shadowView.tag) == - insertedPairs.end()) { - // The new view was (re)inserted, so there is no need to create it. - continue; - } - - createMutations.push_back( - ShadowViewMutation::CreateMutation(newChildPair.shadowView)); - - calculateShadowViewMutationsClassic( - downwardMutations, - newChildPair.shadowView, - {}, - sliceChildShadowNodeViewPairs(*newChildPair.shadowNode)); - } - - // All mutations in an optimal order: - std::move( - destructiveDownwardMutations.begin(), - destructiveDownwardMutations.end(), - std::back_inserter(mutations)); - std::move( - updateMutations.begin(), - updateMutations.end(), - std::back_inserter(mutations)); - std::move( - removeMutations.rbegin(), - removeMutations.rend(), - std::back_inserter(mutations)); - std::move( - deleteMutations.begin(), - deleteMutations.end(), - std::back_inserter(mutations)); - std::move( - createMutations.begin(), - createMutations.end(), - std::back_inserter(mutations)); - std::move( - downwardMutations.begin(), - downwardMutations.end(), - std::back_inserter(mutations)); - std::move( - insertMutations.begin(), - insertMutations.end(), - std::back_inserter(mutations)); -} - -static void calculateShadowViewMutationsOptimizedMoves( - ShadowViewMutation::List &mutations, - ShadowView const &parentShadowView, - ShadowViewNodePair::List &&oldChildPairs, - ShadowViewNodePair::List &&newChildPairs) { - if (oldChildPairs.size() == 0 && newChildPairs.size() == 0) { + if (oldChildPairs.empty() && newChildPairs.empty()) { return; } @@ -479,7 +302,7 @@ static void calculateShadowViewMutationsOptimizedMoves( sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); auto newGrandChildPairs = sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( *(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations), oldChildPair.shadowView, @@ -502,7 +325,7 @@ static void calculateShadowViewMutationsOptimizedMoves( // We also have to call the algorithm recursively to clean up the entire // subtree starting from the removed view. - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( destructiveDownwardMutations, oldChildPair.shadowView, sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode), @@ -519,7 +342,7 @@ static void calculateShadowViewMutationsOptimizedMoves( createMutations.push_back( ShadowViewMutation::CreateMutation(newChildPair.shadowView)); - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( downwardMutations, newChildPair.shadowView, {}, @@ -576,7 +399,7 @@ static void calculateShadowViewMutationsOptimizedMoves( sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); auto newGrandChildPairs = sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( *(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations), oldChildPair.shadowView, @@ -617,7 +440,7 @@ static void calculateShadowViewMutationsOptimizedMoves( sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); auto newGrandChildPairs = sliceChildShadowNodeViewPairs(*newChildPair.shadowNode); - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( *(newGrandChildPairs.size() ? &downwardMutations : &destructiveDownwardMutations), oldChildPair.shadowView, @@ -641,7 +464,7 @@ static void calculateShadowViewMutationsOptimizedMoves( // We also have to call the algorithm recursively to clean up the // entire subtree starting from the removed view. - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( destructiveDownwardMutations, oldChildPair.shadowView, sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode), @@ -677,7 +500,7 @@ static void calculateShadowViewMutationsOptimizedMoves( createMutations.push_back( ShadowViewMutation::CreateMutation(newChildPair.shadowView)); - calculateShadowViewMutationsOptimizedMoves( + calculateShadowViewMutations( downwardMutations, newChildPair.shadowView, {}, @@ -717,7 +540,6 @@ static void calculateShadowViewMutationsOptimizedMoves( } ShadowViewMutation::List calculateShadowViewMutations( - DifferentiatorMode differentiatorMode, ShadowNode const &oldRootShadowNode, ShadowNode const &newRootShadowNode) { SystraceSection s("calculateShadowViewMutations"); @@ -736,19 +558,11 @@ ShadowViewMutation::List calculateShadowViewMutations( ShadowView(), oldRootShadowView, newRootShadowView, -1)); } - if (differentiatorMode == DifferentiatorMode::Classic) { - calculateShadowViewMutationsClassic( - mutations, - ShadowView(oldRootShadowNode), - sliceChildShadowNodeViewPairs(oldRootShadowNode), - sliceChildShadowNodeViewPairs(newRootShadowNode)); - } else { - calculateShadowViewMutationsOptimizedMoves( - mutations, - ShadowView(oldRootShadowNode), - sliceChildShadowNodeViewPairs(oldRootShadowNode), - sliceChildShadowNodeViewPairs(newRootShadowNode)); - } + calculateShadowViewMutations( + mutations, + ShadowView(oldRootShadowNode), + sliceChildShadowNodeViewPairs(oldRootShadowNode), + sliceChildShadowNodeViewPairs(newRootShadowNode)); return mutations; } diff --git a/ReactCommon/fabric/mounting/Differentiator.h b/ReactCommon/fabric/mounting/Differentiator.h index 96adb5b6ed536c..a7afd4f21c6b6f 100644 --- a/ReactCommon/fabric/mounting/Differentiator.h +++ b/ReactCommon/fabric/mounting/Differentiator.h @@ -21,7 +21,6 @@ enum class DifferentiatorMode { Classic, OptimizedMoves }; * The list of mutations might be and might not be optimal. */ ShadowViewMutationList calculateShadowViewMutations( - DifferentiatorMode differentiatorMode, ShadowNode const &oldRootShadowNode, ShadowNode const &newRootShadowNode); diff --git a/ReactCommon/fabric/mounting/MountingCoordinator.cpp b/ReactCommon/fabric/mounting/MountingCoordinator.cpp index 396740c1fd9d70..ad1552f1569102 100644 --- a/ReactCommon/fabric/mounting/MountingCoordinator.cpp +++ b/ReactCommon/fabric/mounting/MountingCoordinator.cpp @@ -19,9 +19,12 @@ namespace facebook { namespace react { -MountingCoordinator::MountingCoordinator(ShadowTreeRevision baseRevision) +MountingCoordinator::MountingCoordinator( + ShadowTreeRevision baseRevision, + MountingOverrideDelegate *delegate) : surfaceId_(baseRevision.getRootShadowNode().getSurfaceId()), - baseRevision_(baseRevision) { + baseRevision_(baseRevision), + mountingOverrideDelegate_(delegate) { #ifdef RN_SHADOW_TREE_INTROSPECTION stubViewTree_ = stubViewTreeFromShadowNode(baseRevision_.getRootShadowNode()); #endif @@ -66,33 +69,30 @@ bool MountingCoordinator::waitForTransaction( lock, timeout, [this]() { return lastRevision_.has_value(); }); } -better::optional MountingCoordinator::pullTransaction( - DifferentiatorMode differentiatorMode) const { - std::lock_guard lock(mutex_); - - if (!lastRevision_.has_value()) { - return {}; - } - - number_++; +void MountingCoordinator::updateBaseRevision( + ShadowTreeRevision const &baseRevision) const { + baseRevision_ = std::move(baseRevision); +} - auto telemetry = lastRevision_->getTelemetry(); - telemetry.willDiff(); +void MountingCoordinator::resetLatestRevision() const { + lastRevision_.reset(); +} - auto mutations = calculateShadowViewMutations( - differentiatorMode, - baseRevision_.getRootShadowNode(), - lastRevision_->getRootShadowNode()); +#ifdef RN_SHADOW_TREE_INTROSPECTION +void MountingCoordinator::validateTransactionAgainstStubViewTree( + ShadowViewMutationList const &mutations, + bool assertEquality) const { + std::string line; - telemetry.didDiff(); + std::stringstream ssMutations(getDebugDescription(mutations, {})); + while (std::getline(ssMutations, line, '\n')) { + LOG(ERROR) << "Mutations:" << line; + } -#ifdef RN_SHADOW_TREE_INTROSPECTION stubViewTree_.mutate(mutations); auto stubViewTree = stubViewTreeFromShadowNode(lastRevision_->getRootShadowNode()); - std::string line; - std::stringstream ssOldTree( baseRevision_.getRootShadowNode().getDebugDescription()); while (std::getline(ssOldTree, line, '\n')) { @@ -105,19 +105,65 @@ better::optional MountingCoordinator::pullTransaction( LOG(ERROR) << "New tree:" << line; } - std::stringstream ssMutations(getDebugDescription(mutations, {})); - while (std::getline(ssMutations, line, '\n')) { - LOG(ERROR) << "Mutations:" << line; + if (assertEquality) { + assert(stubViewTree_ == stubViewTree); + } +} +#endif + +better::optional MountingCoordinator::pullTransaction() + const { + std::lock_guard lock(mutex_); + + bool shouldOverridePullTransaction = mountingOverrideDelegate_ != nullptr && + mountingOverrideDelegate_->shouldOverridePullTransaction(); + + if (!shouldOverridePullTransaction && !lastRevision_.has_value()) { + return {}; + } + + number_++; + + ShadowViewMutation::List diffMutations{}; + auto telemetry = + (lastRevision_.hasValue() ? lastRevision_->getTelemetry() + : MountingTelemetry{}); + if (lastRevision_.hasValue()) { + telemetry.willDiff(); + + diffMutations = calculateShadowViewMutations( + baseRevision_.getRootShadowNode(), lastRevision_->getRootShadowNode()); + + telemetry.didDiff(); } - assert(stubViewTree_ == stubViewTree); + better::optional transaction{}; + + // The override delegate can provide custom mounting instructions, + // even if there's no `lastRevision_`. Consider cases of animation frames + // in between React tree updates. + if (shouldOverridePullTransaction) { + transaction = mountingOverrideDelegate_->pullTransaction( + surfaceId_, number_, telemetry, std::move(diffMutations)); + } else if (lastRevision_.hasValue()) { + transaction = MountingTransaction{ + surfaceId_, number_, std::move(diffMutations), telemetry}; + } + + if (lastRevision_.hasValue()) { +#ifdef RN_SHADOW_TREE_INTROSPECTION + // Only validate non-animated transactions - it's garbage to validate + // animated transactions, since the stub view tree likely won't match + // the committed tree during an animation. + this->validateTransactionAgainstStubViewTree( + transaction->getMutations(), !shouldOverridePullTransaction); #endif - baseRevision_ = std::move(*lastRevision_); - lastRevision_.reset(); + baseRevision_ = std::move(*lastRevision_); + lastRevision_.reset(); + } - return MountingTransaction{ - surfaceId_, number_, std::move(mutations), telemetry}; + return transaction; } } // namespace react diff --git a/ReactCommon/fabric/mounting/MountingCoordinator.h b/ReactCommon/fabric/mounting/MountingCoordinator.h index 3926f49618b276..6976f35f48b401 100644 --- a/ReactCommon/fabric/mounting/MountingCoordinator.h +++ b/ReactCommon/fabric/mounting/MountingCoordinator.h @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include "ShadowTreeRevision.h" #ifdef RN_SHADOW_TREE_INTROSPECTION #include @@ -33,10 +35,12 @@ class MountingCoordinator final { using Shared = std::shared_ptr; /* - * The constructor is ment to be used only inside `ShadowTree`, and it's + * The constructor is meant to be used only inside `ShadowTree`, and it's * `public` only to enable using with `std::make_shared<>`. */ - MountingCoordinator(ShadowTreeRevision baseRevision); + MountingCoordinator( + ShadowTreeRevision baseRevision, + MountingOverrideDelegate *delegate); /* * Returns the id of the surface that the coordinator belongs to. @@ -52,8 +56,7 @@ class MountingCoordinator final { * However, a consumer should always call it on the same thread (e.g. on the * main thread) or ensure sequentiality of mount transactions separately. */ - better::optional pullTransaction( - DifferentiatorMode differentiatorMode) const; + better::optional pullTransaction() const; /* * Blocks the current thread until a new mounting transaction is available or @@ -66,12 +69,20 @@ class MountingCoordinator final { */ bool waitForTransaction(std::chrono::duration timeout) const; - private: - friend class ShadowTree; + /* + * Methods from this section are meant to be used by + * `MountingOverrideDelegate` only. + */ + public: + void updateBaseRevision(ShadowTreeRevision const &baseRevision) const; + void resetLatestRevision() const; /* * Methods from this section are meant to be used by `ShadowTree` only. */ + private: + friend class ShadowTree; + void push(ShadowTreeRevision &&revision) const; /* @@ -79,7 +90,7 @@ class MountingCoordinator final { * Generating a `MountingTransaction` requires some resources which the * `MountingCoordinator` does not own (e.g. `ComponentDescriptor`s). Revoking * committed revisions allows the owner (a Shadow Tree) to make sure that - * those resources will not be accessed (e.g. by the Mouting Layer). + * those resources will not be accessed (e.g. by the Mounting Layer). */ void revoke() const; @@ -91,8 +102,12 @@ class MountingCoordinator final { mutable better::optional lastRevision_{}; mutable MountingTransaction::Number number_{0}; mutable std::condition_variable signal_; + mutable MountingOverrideDelegate *mountingOverrideDelegate_{nullptr}; #ifdef RN_SHADOW_TREE_INTROSPECTION + void validateTransactionAgainstStubViewTree( + ShadowViewMutationList const &mutations, + bool assertEquality) const; mutable StubViewTree stubViewTree_; // Protected by `mutex_`. #endif }; diff --git a/ReactCommon/fabric/mounting/MountingOverrideDelegate.h b/ReactCommon/fabric/mounting/MountingOverrideDelegate.h new file mode 100644 index 00000000000000..7f642078101e38 --- /dev/null +++ b/ReactCommon/fabric/mounting/MountingOverrideDelegate.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#pragma once + +namespace facebook { +namespace react { + +class MountingCoordinator; + +/** + * Generic interface for anything that needs to override specific + * MountingCoordinator methods. This is for platform-specific escape hatches + * like animations. + */ +class MountingOverrideDelegate { + public: + virtual bool shouldOverridePullTransaction() const = 0; + virtual ~MountingOverrideDelegate(){}; + + /** + * Delegates that override this method are responsible for: + * + * - Returning a MountingTransaction with mutations + * - Calling + * - Telemetry, if appropriate + * + * @param surfaceId + * @param number + * @param mountingCoordinator + * @return + */ + virtual better::optional pullTransaction( + SurfaceId surfaceId, + MountingTransaction::Number number, + MountingTelemetry const &telemetry, + ShadowViewMutationList mutations) const = 0; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/MountingTransaction.h b/ReactCommon/fabric/mounting/MountingTransaction.h index a13526f8a900e8..0a0c68ab6d0ea9 100644 --- a/ReactCommon/fabric/mounting/MountingTransaction.h +++ b/ReactCommon/fabric/mounting/MountingTransaction.h @@ -18,7 +18,7 @@ namespace react { * particularly list of mutations and meta-data associated with the commit. * Movable and copyable, but moving is strongly encouraged. * Beware: A moved-from object of this type has unspecified value and accessing - * that is UB. + * that is UB (Undefined Behaviour). */ class MountingTransaction final { public: diff --git a/ReactCommon/fabric/mounting/ShadowTree.cpp b/ReactCommon/fabric/mounting/ShadowTree.cpp index 654d26fed0558a..d6a40be91705ef 100644 --- a/ReactCommon/fabric/mounting/ShadowTree.cpp +++ b/ReactCommon/fabric/mounting/ShadowTree.cpp @@ -41,7 +41,7 @@ static ShadowNode::Unshared progressState(ShadowNode const &shadowNode) { } auto newChildren = ShadowNode::ListOfShared{}; - if (shadowNode.getChildren().size() > 0) { + if (!shadowNode.getChildren().empty()) { auto index = size_t{0}; for (auto const &childNode : shadowNode.getChildren()) { auto newChildNode = progressState(*childNode); @@ -170,7 +170,7 @@ static void updateMountedFlag( return; } - if (oldChildren.size() == 0 && newChildren.size() == 0) { + if (oldChildren.empty() && newChildren.empty()) { // Both lists are empty, nothing to do. return; } @@ -221,7 +221,8 @@ ShadowTree::ShadowTree( LayoutConstraints const &layoutConstraints, LayoutContext const &layoutContext, RootComponentDescriptor const &rootComponentDescriptor, - ShadowTreeDelegate const &delegate) + ShadowTreeDelegate const &delegate, + MountingOverrideDelegate *mountingOverrideDelegate) : surfaceId_(surfaceId), delegate_(delegate) { const auto noopEventEmitter = std::make_shared( nullptr, -1, std::shared_ptr()); @@ -240,7 +241,7 @@ ShadowTree::ShadowTree( family)); mountingCoordinator_ = std::make_shared( - ShadowTreeRevision{rootShadowNode_, 0, {}}); + ShadowTreeRevision{rootShadowNode_, 0, {}}, mountingOverrideDelegate); } ShadowTree::~ShadowTree() { @@ -357,7 +358,7 @@ bool ShadowTree::tryCommit( mountingCoordinator_->push( ShadowTreeRevision{newRootShadowNode, revisionNumber, telemetry}); - delegate_.shadowTreeDidFinishTransaction(*this, mountingCoordinator_); + notifyDelegatesOfUpdates(); return true; } @@ -398,5 +399,9 @@ void ShadowTree::emitLayoutEvents( } } +void ShadowTree::notifyDelegatesOfUpdates() const { + delegate_.shadowTreeDidFinishTransaction(*this, mountingCoordinator_); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/mounting/ShadowTree.h b/ReactCommon/fabric/mounting/ShadowTree.h index f0c904d5159a0c..a4606ba9a88dd5 100644 --- a/ReactCommon/fabric/mounting/ShadowTree.h +++ b/ReactCommon/fabric/mounting/ShadowTree.h @@ -18,6 +18,7 @@ #include #include #include +#include "MountingOverrideDelegate.h" namespace facebook { namespace react { @@ -38,7 +39,8 @@ class ShadowTree final { LayoutConstraints const &layoutConstraints, LayoutContext const &layoutContext, RootComponentDescriptor const &rootComponentDescriptor, - ShadowTreeDelegate const &delegate); + ShadowTreeDelegate const &delegate, + MountingOverrideDelegate *mountingOverrideDelegate); ~ShadowTree(); @@ -69,6 +71,13 @@ class ShadowTree final { */ void commitEmptyTree() const; + /** + * Forces the ShadowTree to ping its delegate that an update is available. + * Useful for animations on Android. + * @return + */ + void notifyDelegatesOfUpdates() const; + MountingCoordinator::Shared getMountingCoordinator() const; /* diff --git a/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp b/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp index f2792af365b24d..1564bbee14bd04 100644 --- a/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp +++ b/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp @@ -12,8 +12,7 @@ namespace react { ShadowTreeRegistry::~ShadowTreeRegistry() { assert( - registry_.size() == 0 && - "Deallocation of non-empty `ShadowTreeRegistry`."); + registry_.empty() && "Deallocation of non-empty `ShadowTreeRegistry`."); } void ShadowTreeRegistry::add(std::unique_ptr &&shadowTree) const { diff --git a/ReactCommon/fabric/mounting/ShadowTreeRevision.cpp b/ReactCommon/fabric/mounting/ShadowTreeRevision.cpp index 7e13c855403a7e..46f59d041804e7 100644 --- a/ReactCommon/fabric/mounting/ShadowTreeRevision.cpp +++ b/ReactCommon/fabric/mounting/ShadowTreeRevision.cpp @@ -22,6 +22,10 @@ MountingTelemetry const &ShadowTreeRevision::getTelemetry() const { return telemetry_; } +ShadowNode::Shared ShadowTreeRevision::getSharedRootShadowNode() { + return rootShadowNode_; +} + ShadowNode const &ShadowTreeRevision::getRootShadowNode() { return *rootShadowNode_; } diff --git a/ReactCommon/fabric/mounting/ShadowTreeRevision.h b/ReactCommon/fabric/mounting/ShadowTreeRevision.h index 71f68d0ed68dd2..ba89dc784f8364 100644 --- a/ReactCommon/fabric/mounting/ShadowTreeRevision.h +++ b/ReactCommon/fabric/mounting/ShadowTreeRevision.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -42,14 +43,21 @@ class ShadowTreeRevision final { */ MountingTelemetry const &getTelemetry() const; - private: - friend class MountingCoordinator; + /* + * Methods from this section are meant to be used by + * `MountingOverrideDelegate` only. + */ + public: + ShadowNode const &getRootShadowNode(); + ShadowNode::Shared getSharedRootShadowNode(); /* * Methods from this section are meant to be used by `MountingCoordinator` * only. */ - ShadowNode const &getRootShadowNode(); + private: + friend class MountingCoordinator; + Number getNumber() const; private: diff --git a/ReactCommon/fabric/mounting/ShadowView.cpp b/ReactCommon/fabric/mounting/ShadowView.cpp index 800308add54d1c..a5436df7dc35df 100644 --- a/ReactCommon/fabric/mounting/ShadowView.cpp +++ b/ReactCommon/fabric/mounting/ShadowView.cpp @@ -13,10 +13,10 @@ namespace facebook { namespace react { static LayoutMetrics layoutMetricsFromShadowNode(ShadowNode const &shadowNode) { - auto layotableShadowNode = + auto layoutableShadowNode = traitCast(&shadowNode); - return layotableShadowNode ? layotableShadowNode->getLayoutMetrics() - : EmptyLayoutMetrics; + return layoutableShadowNode ? layoutableShadowNode->getLayoutMetrics() + : EmptyLayoutMetrics; } ShadowView::ShadowView(const ShadowNode &shadowNode) diff --git a/ReactCommon/fabric/mounting/ShadowView.h b/ReactCommon/fabric/mounting/ShadowView.h index 1372b44928af37..fc51f9e9b1b6d5 100644 --- a/ReactCommon/fabric/mounting/ShadowView.h +++ b/ReactCommon/fabric/mounting/ShadowView.h @@ -66,7 +66,7 @@ struct ShadowViewNodePair final { ShadowNode const *shadowNode; /* - * The stored pointer to `ShadowNode` represents an indentity of the pair. + * The stored pointer to `ShadowNode` represents an identity of the pair. */ bool operator==(const ShadowViewNodePair &rhs) const; bool operator!=(const ShadowViewNodePair &rhs) const; diff --git a/ReactCommon/fabric/mounting/tests/MountingTest.cpp b/ReactCommon/fabric/mounting/tests/MountingTest.cpp index 52361e23dc5ab3..517347986614dc 100644 --- a/ReactCommon/fabric/mounting/tests/MountingTest.cpp +++ b/ReactCommon/fabric/mounting/tests/MountingTest.cpp @@ -227,8 +227,7 @@ TEST(MountingTest, testMinimalInstructionGeneration) { }*/ // Calculating mutations. - auto mutations1 = calculateShadowViewMutations( - DifferentiatorMode::OptimizedMoves, *rootNodeV1, *rootNodeV2); + auto mutations1 = calculateShadowViewMutations(*rootNodeV1, *rootNodeV2); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -244,8 +243,7 @@ TEST(MountingTest, testMinimalInstructionGeneration) { assert(mutations1[1].index == 0); // Calculating mutations. - auto mutations2 = calculateShadowViewMutations( - DifferentiatorMode::OptimizedMoves, *rootNodeV2, *rootNodeV3); + auto mutations2 = calculateShadowViewMutations(*rootNodeV2, *rootNodeV3); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -261,8 +259,7 @@ TEST(MountingTest, testMinimalInstructionGeneration) { assert(mutations2[1].oldChildShadowView.tag == 100); // Calculating mutations. - auto mutations3 = calculateShadowViewMutations( - DifferentiatorMode::OptimizedMoves, *rootNodeV3, *rootNodeV4); + auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -283,8 +280,7 @@ TEST(MountingTest, testMinimalInstructionGeneration) { assert(mutations3[3].index == 2); // Calculating mutations. - auto mutations4 = calculateShadowViewMutations( - DifferentiatorMode::OptimizedMoves, *rootNodeV4, *rootNodeV5); + auto mutations4 = calculateShadowViewMutations(*rootNodeV4, *rootNodeV5); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -309,8 +305,7 @@ TEST(MountingTest, testMinimalInstructionGeneration) { assert(mutations4[5].newChildShadowView.tag == 102); assert(mutations4[5].index == 3); - auto mutations5 = calculateShadowViewMutations( - DifferentiatorMode::OptimizedMoves, *rootNodeV5, *rootNodeV6); + auto mutations5 = calculateShadowViewMutations(*rootNodeV5, *rootNodeV6); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -329,8 +324,7 @@ TEST(MountingTest, testMinimalInstructionGeneration) { assert(mutations5[3].newChildShadowView.tag == 105); assert(mutations5[3].index == 3); - auto mutations6 = calculateShadowViewMutations( - DifferentiatorMode::OptimizedMoves, *rootNodeV6, *rootNodeV7); + auto mutations6 = calculateShadowViewMutations(*rootNodeV6, *rootNodeV7); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. diff --git a/ReactCommon/fabric/mounting/tests/ShadowTreeLifeCycleTest.cpp b/ReactCommon/fabric/mounting/tests/ShadowTreeLifeCycleTest.cpp index ab2ad89b990449..c3aef70d4ab9c2 100644 --- a/ReactCommon/fabric/mounting/tests/ShadowTreeLifeCycleTest.cpp +++ b/ReactCommon/fabric/mounting/tests/ShadowTreeLifeCycleTest.cpp @@ -20,7 +20,6 @@ namespace facebook { namespace react { static void testShadowNodeTreeLifeCycle( - DifferentiatorMode differentiatorMode, uint_fast32_t seed, int treeSize, int repeats, @@ -72,8 +71,8 @@ static void testShadowNodeTreeLifeCycle( // Building an initial view hierarchy. auto viewTree = stubViewTreeFromShadowNode(*emptyRootNode); - viewTree.mutate(calculateShadowViewMutations( - differentiatorMode, *emptyRootNode, *currentRootNode)); + viewTree.mutate( + calculateShadowViewMutations(*emptyRootNode, *currentRootNode)); for (int j = 0; j < stages; j++) { auto nextRootNode = currentRootNode; @@ -99,8 +98,8 @@ static void testShadowNodeTreeLifeCycle( allNodes.push_back(nextRootNode); // Calculating mutations. - auto mutations = calculateShadowViewMutations( - differentiatorMode, *currentRootNode, *nextRootNode); + auto mutations = + calculateShadowViewMutations(*currentRootNode, *nextRootNode); // Mutating the view tree. viewTree.mutate(mutations); @@ -148,27 +147,8 @@ static void testShadowNodeTreeLifeCycle( using namespace facebook::react; -TEST(MountingTest, stableBiggerTreeFewerIterationsClassic) { - testShadowNodeTreeLifeCycle( - DifferentiatorMode::Classic, - /* seed */ 1, - /* size */ 512, - /* repeats */ 32, - /* stages */ 32); -} - -TEST(MountingTest, stableSmallerTreeMoreIterationsClassic) { - testShadowNodeTreeLifeCycle( - DifferentiatorMode::Classic, - /* seed */ 1, - /* size */ 16, - /* repeats */ 512, - /* stages */ 32); -} - TEST(MountingTest, stableBiggerTreeFewerIterationsOptimizedMoves) { testShadowNodeTreeLifeCycle( - DifferentiatorMode::OptimizedMoves, /* seed */ 0, /* size */ 512, /* repeats */ 32, @@ -177,7 +157,6 @@ TEST(MountingTest, stableBiggerTreeFewerIterationsOptimizedMoves) { TEST(MountingTest, stableSmallerTreeMoreIterationsOptimizedMoves) { testShadowNodeTreeLifeCycle( - DifferentiatorMode::OptimizedMoves, /* seed */ 0, /* size */ 16, /* repeats */ 512, diff --git a/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp b/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp index 19ba086ca2e3e3..665f18bcf6c0c4 100644 --- a/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp +++ b/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp @@ -103,7 +103,8 @@ TEST(StateReconciliationTest, testStateReconciliation) { LayoutConstraints{}, LayoutContext{}, rootComponentDescriptor, - shadowTreeDelegate}; + shadowTreeDelegate, + nullptr}; shadowTree.commit( [&](RootShadowNode::Shared const &oldRootShadowNode) { diff --git a/ReactCommon/fabric/scheduler/BUCK b/ReactCommon/fabric/scheduler/BUCK index e65958365b3007..fc4a94488bdfc1 100644 --- a/ReactCommon/fabric/scheduler/BUCK +++ b/ReactCommon/fabric/scheduler/BUCK @@ -36,11 +36,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/scheduler/Scheduler.cpp b/ReactCommon/fabric/scheduler/Scheduler.cpp index e9db21ae7e7812..d4dafd716b69c9 100644 --- a/ReactCommon/fabric/scheduler/Scheduler.cpp +++ b/ReactCommon/fabric/scheduler/Scheduler.cpp @@ -13,15 +13,23 @@ #include #include #include +#include +#include #include #include #include +#ifdef RN_SHADOW_TREE_INTROSPECTION +#include +#include +#endif + namespace facebook { namespace react { Scheduler::Scheduler( SchedulerToolbox schedulerToolbox, + UIManagerAnimationDelegate *animationDelegate, SchedulerDelegate *delegate) { runtimeExecutor_ = schedulerToolbox.runtimeExecutor; @@ -29,8 +37,13 @@ Scheduler::Scheduler( schedulerToolbox.contextContainer ->at>("ReactNativeConfig"); + // Creating a container for future `EventDispatcher` instance. + eventDispatcher_ = + std::make_shared>(); + auto uiManager = std::make_shared(); auto eventOwnerBox = std::make_shared(); + eventOwnerBox->owner = eventDispatcher_; auto eventPipe = [uiManager]( jsi::Runtime &runtime, @@ -47,20 +60,24 @@ Scheduler::Scheduler( uiManager->updateState(stateUpdate); }; - eventDispatcher_ = std::make_shared( + // Creating an `EventDispatcher` instance inside the already allocated + // container (inside the optional). + eventDispatcher_->emplace( eventPipe, statePipe, schedulerToolbox.synchronousEventBeatFactory, schedulerToolbox.asynchronousEventBeatFactory, eventOwnerBox); - eventOwnerBox->owner = eventDispatcher_; + // Casting to `std::shared_ptr`. + auto eventDispatcher = + EventDispatcher::Shared{eventDispatcher_, &eventDispatcher_->value()}; componentDescriptorRegistry_ = schedulerToolbox.componentRegistryFactory( - eventDispatcher_, schedulerToolbox.contextContainer); + eventDispatcher, schedulerToolbox.contextContainer); rootComponentDescriptor_ = std::make_unique( - ComponentDescriptorParameters{eventDispatcher_, nullptr, nullptr}); + ComponentDescriptorParameters{eventDispatcher, nullptr, nullptr}); uiManager->setDelegate(this); uiManager->setComponentDescriptorRegistry(componentDescriptorRegistry_); @@ -81,12 +98,22 @@ Scheduler::Scheduler( delegate_ = delegate; uiManager_ = uiManager; + if (animationDelegate != nullptr) { + animationDelegate->setComponentDescriptorRegistry( + componentDescriptorRegistry_); + } + uiManager_->setAnimationDelegate(animationDelegate); + #ifdef ANDROID enableNewStateReconciliation_ = reactNativeConfig_->getBool( "react_fabric:enable_new_state_reconciliation_android"); + removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool( + "react_fabric:remove_outstanding_surfaces_on_destruction_android"); #else enableNewStateReconciliation_ = reactNativeConfig_->getBool( "react_fabric:enable_new_state_reconciliation_ios"); + removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool( + "react_fabric:remove_outstanding_surfaces_on_destruction_ios"); #endif } @@ -109,10 +136,10 @@ Scheduler::~Scheduler() { }); assert( - surfaceIds.size() == 0 && + surfaceIds.empty() && "Scheduler was destroyed with outstanding Surfaces."); - if (surfaceIds.size() == 0) { + if (surfaceIds.empty()) { return; } @@ -131,6 +158,12 @@ Scheduler::~Scheduler() { uiManager_->getShadowTreeRegistry().visit( surfaceId, [](ShadowTree const &shadowTree) { shadowTree.commitEmptyTree(); }); + + // Removing surfaces is gated because it acquires mutex waiting for commits + // in flight; in theory, it can deadlock. + if (removeOutstandingSurfacesOnDestruction_) { + uiManager_->getShadowTreeRegistry().remove(surfaceId); + } } } @@ -139,7 +172,8 @@ void Scheduler::startSurface( const std::string &moduleName, const folly::dynamic &initialProps, const LayoutConstraints &layoutConstraints, - const LayoutContext &layoutContext) const { + const LayoutContext &layoutContext, + MountingOverrideDelegate *mountingOverrideDelegate) const { SystraceSection s("Scheduler::startSurface"); auto shadowTree = std::make_unique( @@ -147,7 +181,8 @@ void Scheduler::startSurface( layoutConstraints, layoutContext, *rootComponentDescriptor_, - *uiManager_); + *uiManager_, + mountingOverrideDelegate); shadowTree->setEnableNewStateReconciliation(enableNewStateReconciliation_); @@ -168,7 +203,7 @@ void Scheduler::renderTemplateToSurface( const std::string &uiTemplate) { SystraceSection s("Scheduler::renderTemplateToSurface"); try { - if (uiTemplate.size() == 0) { + if (uiTemplate.empty()) { return; } NativeModuleRegistry nMR; @@ -291,6 +326,12 @@ SchedulerDelegate *Scheduler::getDelegate() const { return delegate_; } +#pragma mark - UIManagerAnimationDelegate + +void Scheduler::animationTick() const { + uiManager_->animationTick(); +} + #pragma mark - UIManagerDelegate void Scheduler::uiManagerDidFinishTransaction( @@ -301,7 +342,6 @@ void Scheduler::uiManagerDidFinishTransaction( delegate_->schedulerDidFinishTransaction(mountingCoordinator); } } - void Scheduler::uiManagerDidCreateShadowNode( const ShadowNode::Shared &shadowNode) { SystraceSection s("Scheduler::uiManagerDidCreateShadowNode"); diff --git a/ReactCommon/fabric/scheduler/Scheduler.h b/ReactCommon/fabric/scheduler/Scheduler.h index 07799c62f1fa46..9ee114ec21edc1 100644 --- a/ReactCommon/fabric/scheduler/Scheduler.h +++ b/ReactCommon/fabric/scheduler/Scheduler.h @@ -12,13 +12,14 @@ #include #include -#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -31,7 +32,10 @@ namespace react { */ class Scheduler final : public UIManagerDelegate { public: - Scheduler(SchedulerToolbox schedulerToolbox, SchedulerDelegate *delegate); + Scheduler( + SchedulerToolbox schedulerToolbox, + UIManagerAnimationDelegate *animationDelegate, + SchedulerDelegate *delegate); ~Scheduler(); #pragma mark - Surface Management @@ -41,7 +45,8 @@ class Scheduler final : public UIManagerDelegate { const std::string &moduleName, const folly::dynamic &initialProps, const LayoutConstraints &layoutConstraints = {}, - const LayoutContext &layoutContext = {}) const; + const LayoutContext &layoutContext = {}, + MountingOverrideDelegate *mountingOverrideDelegate = nullptr) const; void renderTemplateToSurface( SurfaceId surfaceId, @@ -88,6 +93,13 @@ class Scheduler final : public UIManagerDelegate { void setDelegate(SchedulerDelegate *delegate); SchedulerDelegate *getDelegate() const; +#pragma mark - UIManagerAnimationDelegate + // This is not needed on iOS or any platform that has a "pull" instead of + // "push" MountingCoordinator model. This just tells the delegate an update + // is available and that it should `pullTransaction`; we may want to rename + // this to be more generic and not animation-specific. + void animationTick() const; + #pragma mark - UIManagerDelegate void uiManagerDidFinishTransaction( @@ -111,8 +123,21 @@ class Scheduler final : public UIManagerDelegate { RuntimeExecutor runtimeExecutor_; std::shared_ptr uiManager_; std::shared_ptr reactNativeConfig_; - EventDispatcher::Shared eventDispatcher_; + + /* + * At some point, we have to have an owning shared pointer to something that + * will become an `EventDispatcher` a moment later. That's why we have it as a + * pointer to an optional: we construct the pointer first, share that with + * parts that need to have ownership (and only ownership) of that, and then + * fill the optional. + */ + std::shared_ptr> eventDispatcher_; + + /* + * Temporary flags. + */ bool enableNewStateReconciliation_{false}; + bool removeOutstandingSurfacesOnDestruction_{false}; }; } // namespace react diff --git a/ReactCommon/fabric/scheduler/SchedulerToolbox.h b/ReactCommon/fabric/scheduler/SchedulerToolbox.h index 87462d89377ff1..0f56dbcccb410b 100644 --- a/ReactCommon/fabric/scheduler/SchedulerToolbox.h +++ b/ReactCommon/fabric/scheduler/SchedulerToolbox.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace facebook { namespace react { @@ -36,6 +37,11 @@ struct SchedulerToolbox final { */ RuntimeExecutor runtimeExecutor; + /* + * Represent connections with a platform-specific UI run loops. + */ + RunLoopObserver::Factory mainRunLoopObserverFactory; + /* * Asynchronous & synchronous event beats. * Represent connections with the platform-specific run loops and general diff --git a/ReactCommon/fabric/templateprocessor/BUCK b/ReactCommon/fabric/templateprocessor/BUCK index 168a55862964e0..ea5518b3818a5b 100644 --- a/ReactCommon/fabric/templateprocessor/BUCK +++ b/ReactCommon/fabric/templateprocessor/BUCK @@ -37,11 +37,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/textlayoutmanager/BUCK b/ReactCommon/fabric/textlayoutmanager/BUCK index 720f3d3e8e2344..90db8d8d71ecae 100644 --- a/ReactCommon/fabric/textlayoutmanager/BUCK +++ b/ReactCommon/fabric/textlayoutmanager/BUCK @@ -75,14 +75,12 @@ rn_xplat_cxx_library( ], prefix = "", ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( [ "platform/android/**/*.cpp", ], ), fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, ios_deps = [ @@ -110,6 +108,7 @@ rn_xplat_cxx_library( "platform/ios/**/*.mm", ], ), + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp index 5a1aab941997e1..52fda0f7cd841f 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp +++ b/ReactCommon/fabric/textlayoutmanager/platform/android/TextLayoutManager.cpp @@ -50,7 +50,7 @@ TextMeasurement TextLayoutManager::doMeasure( } } auto env = Environment::current(); - auto attachmentPositions = env->NewIntArray(attachmentsCount * 2); + auto attachmentPositions = env->NewFloatArray(attachmentsCount * 2); static auto measure = jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") @@ -64,7 +64,7 @@ TextMeasurement TextLayoutManager::doMeasure( jfloat, jfloat, jfloat, - jintArray)>("measure"); + jfloatArray)>("measure"); auto minimumSize = layoutConstraints.minimumSize; auto maximumSize = layoutConstraints.maximumSize; @@ -93,7 +93,7 @@ TextMeasurement TextLayoutManager::doMeasure( maximumSize.height, attachmentPositions)); - jint *attachmentData = env->GetIntArrayElements(attachmentPositions, 0); + jfloat *attachmentData = env->GetFloatArrayElements(attachmentPositions, 0); auto attachments = TextMeasurement::Attachments{}; if (attachmentsCount > 0) { @@ -104,8 +104,8 @@ TextMeasurement TextLayoutManager::doMeasure( if (fragment["isAttachment"] == true) { float top = attachmentData[attachmentIndex * 2]; float left = attachmentData[attachmentIndex * 2 + 1]; - float width = fragment["width"].getInt(); - float height = fragment["height"].getInt(); + float width = (float)fragment["width"].getDouble(); + float height = (float)fragment["height"].getDouble(); auto rect = facebook::react::Rect{{left, top}, facebook::react::Size{width, height}}; diff --git a/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.cpp b/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.cpp index d48c788fdd0ccd..ffdfeae66d547e 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.cpp +++ b/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.cpp @@ -16,11 +16,11 @@ void *TextLayoutManager::getNativeTextLayoutManager() const { return self_; } -Size TextLayoutManager::measure( +TextMeasurement TextLayoutManager::measure( AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const { - return Size{0, 0}; + return TextMeasurement{{0, 0}, {}}; } } // namespace react diff --git a/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.h b/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.h index c4cd7a8318f45d..57d301995b3918 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.h +++ b/ReactCommon/fabric/textlayoutmanager/platform/cxx/TextLayoutManager.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace facebook { @@ -34,7 +35,7 @@ class TextLayoutManager { /* * Measures `attributedStringBox` using native text rendering infrastructure. */ - Size measure( + TextMeasurement measure( AttributedStringBox attributedStringBox, ParagraphAttributes paragraphAttributes, LayoutConstraints layoutConstraints) const; diff --git a/ReactCommon/fabric/uimanager/BUCK b/ReactCommon/fabric/uimanager/BUCK index 0d46cf3f3fea90..aa31fcae2af8c2 100644 --- a/ReactCommon/fabric/uimanager/BUCK +++ b/ReactCommon/fabric/uimanager/BUCK @@ -37,11 +37,10 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/fabric/uimanager/LayoutAnimationStatusDelegate.h b/ReactCommon/fabric/uimanager/LayoutAnimationStatusDelegate.h new file mode 100644 index 00000000000000..b87b96f68f73cc --- /dev/null +++ b/ReactCommon/fabric/uimanager/LayoutAnimationStatusDelegate.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook { +namespace react { + +class LayoutAnimationStatusDelegate { + public: + /** + * Called when the LayoutAnimation engine state changes from animation nothing + * to animating something. This will only be called when you go from 0 to N>0 + * active animations, N to N+1 animations will not result in this being + * called. + */ + virtual void onAnimationStarted() = 0; + + /** + * Called when the LayoutAnimation engine completes all pending animations. + */ + virtual void onAllAnimationsComplete() = 0; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/uimanager/UIManager.cpp b/ReactCommon/fabric/uimanager/UIManager.cpp index e87280c58cd28a..b4910cee67282c 100644 --- a/ReactCommon/fabric/uimanager/UIManager.cpp +++ b/ReactCommon/fabric/uimanager/UIManager.cpp @@ -124,31 +124,39 @@ void UIManager::clearJSResponder() const { } } -ShadowNode::Shared const *UIManager::getNewestCloneOfShadowNode( +ShadowNode::Shared UIManager::getNewestCloneOfShadowNode( ShadowNode const &shadowNode) const { auto findNewestChildInParent = - [&](auto const &parentNode) -> ShadowNode::Shared const * { + [&](auto const &parentNode) -> ShadowNode::Shared { for (auto const &child : parentNode.getChildren()) { if (ShadowNode::sameFamily(*child, shadowNode)) { - return &child; + return child; } } return nullptr; }; - ShadowNode const *ancestorShadowNode; + auto ancestorShadowNode = ShadowNode::Shared{}; shadowTreeRegistry_.visit( shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) { shadowTree.tryCommit( [&](RootShadowNode::Shared const &oldRootShadowNode) { - ancestorShadowNode = oldRootShadowNode.get(); + ancestorShadowNode = oldRootShadowNode; return nullptr; }, true); }); + if (!ancestorShadowNode) { + return nullptr; + } + auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode); + if (ancestors.empty()) { + return nullptr; + } + return findNewestChildInParent(ancestors.rbegin()->first.get()); } @@ -156,7 +164,7 @@ ShadowNode::Shared UIManager::findNodeAtPoint( ShadowNode::Shared const &node, Point point) const { return LayoutableShadowNode::findNodeAtPoint( - *getNewestCloneOfShadowNode(*node), point); + getNewestCloneOfShadowNode(*node), point); } void UIManager::setNativeProps( @@ -190,6 +198,10 @@ LayoutMetrics UIManager::getRelativeLayoutMetrics( LayoutableShadowNode::LayoutInspectingPolicy policy) const { SystraceSection s("UIManager::getRelativeLayoutMetrics"); + // We might store here an owning pointer to `ancestorShadowNode` to ensure + // that the node is not deallocated during method execution lifetime. + auto owningAncestorShadowNode = ShadowNode::Shared{}; + if (!ancestorShadowNode) { shadowTreeRegistry_.visit( shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) { @@ -201,26 +213,22 @@ LayoutMetrics UIManager::getRelativeLayoutMetrics( true); }); } else { - ancestorShadowNode = getNewestCloneOfShadowNode(*ancestorShadowNode)->get(); + // It is possible for JavaScript (or other callers) to have a reference + // to a previous version of ShadowNodes, but we enforce that + // metrics are only calculated on most recently committed versions. + owningAncestorShadowNode = getNewestCloneOfShadowNode(*ancestorShadowNode); + ancestorShadowNode = owningAncestorShadowNode.get(); } - // Get latest version of both the ShadowNode and its ancestor. - // It is possible for JS (or other callers) to have a reference - // to a previous version of ShadowNodes, but we enforce that - // metrics are only calculated on most recently committed versions. - auto newestShadowNode = getNewestCloneOfShadowNode(shadowNode); - - auto layoutableShadowNode = - traitCast(newestShadowNode->get()); auto layoutableAncestorShadowNode = traitCast(ancestorShadowNode); - if (!layoutableShadowNode || !layoutableAncestorShadowNode) { + if (!layoutableAncestorShadowNode) { return EmptyLayoutMetrics; } - return layoutableShadowNode->getRelativeLayoutMetrics( - *layoutableAncestorShadowNode, policy); + return LayoutableShadowNode::computeRelativeLayoutMetrics( + shadowNode.getFamily(), *layoutableAncestorShadowNode, policy); } void UIManager::updateState(StateUpdate const &stateUpdate) const { @@ -260,9 +268,15 @@ void UIManager::dispatchCommand( } void UIManager::configureNextLayoutAnimation( - const folly::dynamic config, + RawValue const &config, SharedEventTarget successCallback, - SharedEventTarget errorCallback) const {} + SharedEventTarget errorCallback) const { + if (animationDelegate_) { + animationDelegate_->uiManagerDidConfigureNextLayoutAnimation( + config, successCallback, errorCallback); + } +} + void UIManager::setComponentDescriptorRegistry( const SharedComponentDescriptorRegistry &componentDescriptorRegistry) { componentDescriptorRegistry_ = componentDescriptorRegistry; @@ -302,5 +316,22 @@ void UIManager::shadowTreeDidFinishTransaction( } } +#pragma mark - UIManagerAnimationDelegate + +void UIManager::setAnimationDelegate( + UIManagerAnimationDelegate *delegate) const { + animationDelegate_ = delegate; +} + +void UIManager::animationTick() { + if (animationDelegate_ != nullptr && + animationDelegate_->shouldAnimateFrame()) { + shadowTreeRegistry_.enumerate( + [&](ShadowTree const &shadowTree, bool &stop) { + shadowTree.notifyDelegatesOfUpdates(); + }); + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/uimanager/UIManager.h b/ReactCommon/fabric/uimanager/UIManager.h index 956f459f935cee..543bf97cfa1852 100644 --- a/ReactCommon/fabric/uimanager/UIManager.h +++ b/ReactCommon/fabric/uimanager/UIManager.h @@ -12,11 +12,13 @@ #include #include +#include #include #include #include #include #include +#include #include namespace facebook { @@ -39,6 +41,15 @@ class UIManager final : public ShadowTreeDelegate { void setDelegate(UIManagerDelegate *delegate); UIManagerDelegate *getDelegate(); + /** + * Sets and gets the UIManager's Animation APIs delegate. + * The delegate is stored as a raw pointer, so the owner must null + * the pointer before being destroyed. + */ + void setAnimationDelegate(UIManagerAnimationDelegate *delegate) const; + + void animationTick(); + /* * Provides access to a UIManagerBindging. * The `callback` methods will not be called if the internal pointer to @@ -92,7 +103,7 @@ class UIManager final : public ShadowTreeDelegate { ShadowNode::Shared const &shadowNode, Point point) const; - ShadowNode::Shared const *getNewestCloneOfShadowNode( + ShadowNode::Shared getNewestCloneOfShadowNode( ShadowNode const &shadowNode) const; /* @@ -121,13 +132,15 @@ class UIManager final : public ShadowTreeDelegate { * This API configures a global LayoutAnimation starting from the root node. */ void configureNextLayoutAnimation( - const folly::dynamic config, + RawValue const &config, SharedEventTarget successCallback, SharedEventTarget errorCallback) const; + ShadowTreeRegistry const &getShadowTreeRegistry() const; SharedComponentDescriptorRegistry componentDescriptorRegistry_; UIManagerDelegate *delegate_; + mutable UIManagerAnimationDelegate *animationDelegate_{nullptr}; UIManagerBinding *uiManagerBinding_; ShadowTreeRegistry shadowTreeRegistry_{}; }; diff --git a/ReactCommon/fabric/uimanager/UIManagerAnimationDelegate.h b/ReactCommon/fabric/uimanager/UIManagerAnimationDelegate.h new file mode 100644 index 00000000000000..9dcbef33e1228c --- /dev/null +++ b/ReactCommon/fabric/uimanager/UIManagerAnimationDelegate.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class UIManagerAnimationDelegate { + public: + virtual ~UIManagerAnimationDelegate(){}; + + /* + * Configure a LayoutAnimation. + * TODO: need SurfaceId here + */ + virtual void uiManagerDidConfigureNextLayoutAnimation( + RawValue const &config, + SharedEventTarget successCallback, + SharedEventTarget errorCallback) const = 0; + + /** + * Set ComponentDescriptor registry. + * + * @param componentDescriptorRegistry + */ + virtual void setComponentDescriptorRegistry( + const SharedComponentDescriptorRegistry &componentDescriptorRegistry) = 0; + + /** + * Only needed on Android to drive animations. + */ + virtual bool shouldAnimateFrame() const = 0; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/uimanager/UIManagerBinding.cpp b/ReactCommon/fabric/uimanager/UIManagerBinding.cpp index 4c50095e4416a8..92d20a915cd600 100644 --- a/ReactCommon/fabric/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/fabric/uimanager/UIManagerBinding.cpp @@ -619,16 +619,14 @@ jsi::Value UIManagerBinding::get( const jsi::Value *arguments, size_t count) -> jsi::Value { uiManager->configureNextLayoutAnimation( - commandArgsFromValue( - runtime, - arguments[0]), // TODO T66507273: do a better job of parsing - // these arguments into a real struct / use a - // C++ typed object instead of folly::dynamic + // TODO: pass in JSI value instead of folly::dynamic to RawValue + RawValue(commandArgsFromValue(runtime, arguments[0])), eventTargetFromValue(runtime, arguments[1], -1), eventTargetFromValue(runtime, arguments[2], -1)); return jsi::Value::undefined(); }); } + return jsi::Value::undefined(); } diff --git a/ReactCommon/fabric/uimanager/UIManagerBinding.h b/ReactCommon/fabric/uimanager/UIManagerBinding.h index 75485e6c8ce695..0ecf00c9dd9d62 100644 --- a/ReactCommon/fabric/uimanager/UIManagerBinding.h +++ b/ReactCommon/fabric/uimanager/UIManagerBinding.h @@ -9,6 +9,7 @@ #include #include +#include #include #include diff --git a/ReactCommon/hermes/inspector/BUCK b/ReactCommon/hermes/inspector/BUCK index 9757b023533637..f3ea76a314efc8 100644 --- a/ReactCommon/hermes/inspector/BUCK +++ b/ReactCommon/hermes/inspector/BUCK @@ -50,9 +50,8 @@ fb_xplat_cxx_library( header_namespace = "hermes/inspector", exported_headers = CHROME_EXPORTED_HEADERS, compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()], - fbandroid_labels = ["supermodule:android/default/public.hermes"], fbobjc_header_path_prefix = "hermes/inspector/chrome", - fbobjc_labels = ["supermodule:ios/default/public.hermes"], + labels = ["supermodule:xplat/default/public.hermes"], macosx_tests_override = [], tests = [":chrome-tests"], visibility = [ @@ -118,9 +117,8 @@ fb_xplat_cxx_library( fbandroid_deps = [ "//fbandroid/native/fb:fb", ], - fbandroid_labels = ["supermodule:android/default/public.hermes"], fbobjc_header_path_prefix = "hermes/inspector/detail", - fbobjc_labels = ["supermodule:ios/default/public.hermes"], + labels = ["supermodule:xplat/default/public.hermes"], macosx_tests_override = [], tests = [":detail-tests"], visibility = [ @@ -192,9 +190,8 @@ fb_xplat_cxx_library( exported_headers = INSPECTOR_EXPORTED_HEADERS, compiler_flags = CFLAGS_BY_MODE[hermes_build_mode()], cxx_tests = [":inspector-tests"], - fbandroid_labels = ["supermodule:android/default/public.hermes"], fbobjc_header_path_prefix = "hermes/inspector", - fbobjc_labels = ["supermodule:ios/default/public.hermes"], + labels = ["supermodule:xplat/default/public.hermes"], macosx_tests_override = [], visibility = [ "PUBLIC", diff --git a/ReactCommon/hermes/inspector/Inspector.cpp b/ReactCommon/hermes/inspector/Inspector.cpp index 3cca8ce1553b22..c2b58712119d55 100644 --- a/ReactCommon/hermes/inspector/Inspector.cpp +++ b/ReactCommon/hermes/inspector/Inspector.cpp @@ -120,6 +120,7 @@ Inspector::Inspector( std::lock_guard lock(mutex_); if (pauseOnFirstStatement) { + awaitingDebuggerOnStart_ = true; TRANSITION(std::make_unique(*this)); } else { TRANSITION(std::make_unique(*this)); @@ -137,6 +138,7 @@ Inspector::~Inspector() { void Inspector::installConsoleFunction( jsi::Object &console, + std::shared_ptr &originalConsole, const std::string &name, const std::string &chromeTypeDefault = "") { jsi::Runtime &rt = adapter_->getRuntime(); @@ -150,11 +152,22 @@ void Inspector::installConsoleFunction( rt, nameID, 1, - [weakInspector, chromeType]( + [weakInspector, originalConsole, name, chromeType]( jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { + if (originalConsole) { + auto val = originalConsole->getProperty(runtime, name.c_str()); + if (val.isObject()) { + auto obj = val.getObject(runtime); + if (obj.isFunction(runtime)) { + auto func = obj.getFunction(runtime); + func.call(runtime, args, count); + } + } + } + if (auto inspector = weakInspector.lock()) { jsi::Array argsArray(runtime, count); for (size_t index = 0; index < count; ++index) @@ -170,22 +183,28 @@ void Inspector::installConsoleFunction( void Inspector::installLogHandler() { jsi::Runtime &rt = adapter_->getRuntime(); auto console = jsi::Object(rt); - installConsoleFunction(console, "assert"); - installConsoleFunction(console, "clear"); - installConsoleFunction(console, "debug"); - installConsoleFunction(console, "dir"); - installConsoleFunction(console, "dirxml"); - installConsoleFunction(console, "error"); - installConsoleFunction(console, "group", "startGroup"); - installConsoleFunction(console, "groupCollapsed", "startGroupCollapsed"); - installConsoleFunction(console, "groupEnd", "endGroup"); - installConsoleFunction(console, "info"); - installConsoleFunction(console, "log"); - installConsoleFunction(console, "profile"); - installConsoleFunction(console, "profileEnd"); - installConsoleFunction(console, "table"); - installConsoleFunction(console, "trace"); - installConsoleFunction(console, "warn", "warning"); + auto val = rt.global().getProperty(rt, "console"); + std::shared_ptr originalConsole; + if (val.isObject()) { + originalConsole = std::make_shared(val.getObject(rt)); + } + installConsoleFunction(console, originalConsole, "assert"); + installConsoleFunction(console, originalConsole, "clear"); + installConsoleFunction(console, originalConsole, "debug"); + installConsoleFunction(console, originalConsole, "dir"); + installConsoleFunction(console, originalConsole, "dirxml"); + installConsoleFunction(console, originalConsole, "error"); + installConsoleFunction(console, originalConsole, "group", "startGroup"); + installConsoleFunction( + console, originalConsole, "groupCollapsed", "startGroupCollapsed"); + installConsoleFunction(console, originalConsole, "groupEnd", "endGroup"); + installConsoleFunction(console, originalConsole, "info"); + installConsoleFunction(console, originalConsole, "log"); + installConsoleFunction(console, originalConsole, "profile"); + installConsoleFunction(console, originalConsole, "profileEnd"); + installConsoleFunction(console, originalConsole, "table"); + installConsoleFunction(console, originalConsole, "trace"); + installConsoleFunction(console, originalConsole, "warn", "warning"); rt.global().setProperty(rt, "console", console); } @@ -661,6 +680,10 @@ bool Inspector::isExecutingSupersededFile() { return false; } +bool Inspector::isAwaitingDebuggerOnStart() { + return awaitingDebuggerOnStart_; +} + } // namespace inspector } // namespace hermes } // namespace facebook diff --git a/ReactCommon/hermes/inspector/Inspector.h b/ReactCommon/hermes/inspector/Inspector.h index bf4fc553c1da1f..ac00e670e74095 100644 --- a/ReactCommon/hermes/inspector/Inspector.h +++ b/ReactCommon/hermes/inspector/Inspector.h @@ -229,6 +229,16 @@ class Inspector : public facebook::hermes::debugger::EventObserver, facebook::hermes::debugger::Debugger &debugger, facebook::hermes::debugger::BreakpointID breakpointId) override; + /** + * Get whether we started with pauseOnFirstStatement, and have not yet had a + * debugger attach and ask to resume from that point. This matches the + * semantics of when CDP Debugger.runIfWaitingForDebugger should resume. + * + * It's not named "isPausedOnStart" because the VM and inspector is not + * necessarily paused; we could be in a RunningWaitPause state. + */ + bool isAwaitingDebuggerOnStart(); + private: friend class InspectorState; @@ -292,6 +302,7 @@ class Inspector : public facebook::hermes::debugger::EventObserver, void installConsoleFunction( jsi::Object &console, + std::shared_ptr &originalConsole, const std::string &name, const std::string &chromeType); @@ -333,6 +344,10 @@ class Inspector : public facebook::hermes::debugger::EventObserver, // Trigger a fake console.log if we're currently in a superseded file. void alertIfPausedInSupersededFile(); + + // Are we currently waiting for a debugger to attach, because we + // requested 'pauseOnFirstStatement'? + bool awaitingDebuggerOnStart_; }; } // namespace inspector diff --git a/ReactCommon/hermes/inspector/InspectorState.cpp b/ReactCommon/hermes/inspector/InspectorState.cpp index 7d6c35e05bbaee..38042872472c83 100644 --- a/ReactCommon/hermes/inspector/InspectorState.cpp +++ b/ReactCommon/hermes/inspector/InspectorState.cpp @@ -51,6 +51,10 @@ std::pair InspectorState::RunningDetached::didPause( nullptr, makeContinueCommand()); } +void InspectorState::RunningDetached::onEnter(InspectorState *previous) { + inspector_.awaitingDebuggerOnStart_ = false; +} + std::pair InspectorState::RunningDetached::enable() { return std::make_pair( InspectorState::Running::make(inspector_), true); @@ -172,6 +176,8 @@ void InspectorState::Running::onEnter(InspectorState *prevState) { inspector_.notifyScriptsLoaded(); } } + + inspector_.awaitingDebuggerOnStart_ = false; } void InspectorState::Running::detach( diff --git a/ReactCommon/hermes/inspector/InspectorState.h b/ReactCommon/hermes/inspector/InspectorState.h index 99cbff7e372c50..827f3149f43bb4 100644 --- a/ReactCommon/hermes/inspector/InspectorState.h +++ b/ReactCommon/hermes/inspector/InspectorState.h @@ -52,7 +52,7 @@ class InspectorState { */ /** - * detach clears all debuger state and transitions to RunningDetached. + * detach clears all debugger state and transitions to RunningDetached. */ virtual void detach(std::shared_ptr> promise) { // As we're not attached we'd like for the operation to be idempotent @@ -186,6 +186,8 @@ class InspectorState::RunningDetached : public InspectorState { std::pair didPause(MonitorLock &lock) override; std::pair enable() override; + void onEnter(InspectorState *prevState) override; + bool isRunningDetached() const override { return true; } diff --git a/ReactCommon/hermes/inspector/chrome/Connection.cpp b/ReactCommon/hermes/inspector/chrome/Connection.cpp index b7656bcf22c64d..6beac456b64fcf 100644 --- a/ReactCommon/hermes/inspector/chrome/Connection.cpp +++ b/ReactCommon/hermes/inspector/chrome/Connection.cpp @@ -94,6 +94,7 @@ class Connection::Impl : public inspector::InspectorObserver, const m::heapProfiler::StopTrackingHeapObjectsRequest &req) override; void handle(const m::runtime::EvaluateRequest &req) override; void handle(const m::runtime::GetPropertiesRequest &req) override; + void handle(const m::runtime::RunIfWaitingForDebuggerRequest &req) override; private: std::vector makePropsFromScope( @@ -312,6 +313,9 @@ void Connection::Impl::onPause( note.reason = "exception"; break; case debugger::PauseReason::ScriptLoaded: { + // This case covers both wait-for-debugger and instrumentation + // breakpoints, since both are implemented as pauses on script load. + note.reason = "other"; note.hitBreakpoints = std::vector(); @@ -325,7 +329,8 @@ void Connection::Impl::onPause( // in the extremely unlikely event that it did *and* did it exactly // between us 1. checking that we should stop, and 2. adding the stop // reason here, then just resume and skip sending a pause notification. - if (note.hitBreakpoints->empty()) { + if (!inspector_->isAwaitingDebuggerOnStart() && + note.hitBreakpoints->empty()) { sendNotification = false; inspector_->resume(); } @@ -528,7 +533,7 @@ void Connection::Impl::handle( const m::heapProfiler::StopTrackingHeapObjectsRequest &req) { sendSnapshot( req.id, - "HeapSnapshot.takeHeapSnapshot", + "HeapSnapshot.stopTrackingHeapObjects", req.reportProgress && *req.reportProgress, /* stopStackTraceCapture */ true); } @@ -866,6 +871,16 @@ void Connection::Impl::handle(const m::runtime::GetPropertiesRequest &req) { .thenError(sendErrorToClient(req.id)); } +void Connection::Impl::handle( + const m::runtime::RunIfWaitingForDebuggerRequest &req) { + if (inspector_->isAwaitingDebuggerOnStart()) { + sendResponseToClientViaExecutor(inspector_->resume(), req.id); + } else { + // We weren't awaiting a debugger. Just send an 'ok'. + sendResponseToClientViaExecutor(req.id); + } +} + /* * Send-to-client methods */ diff --git a/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp b/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp index d40c05e14727d9..e136e1c8f194a9 100644 --- a/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp +++ b/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp @@ -1,5 +1,5 @@ // Copyright 2004-present Facebook. All Rights Reserved. -// @generated SignedSource<<575de63c36edd2a9a0f191501fd4f662>> +// @generated SignedSource<> #include "MessageTypes.h" @@ -50,6 +50,8 @@ std::unique_ptr Request::fromJsonThrowOnError(const std::string &str) { makeUnique}, {"Runtime.evaluate", makeUnique}, {"Runtime.getProperties", makeUnique}, + {"Runtime.runIfWaitingForDebugger", + makeUnique}, }; dynamic obj = folly::parseJson(str); @@ -783,6 +785,28 @@ void runtime::GetPropertiesRequest::accept(RequestHandler &handler) const { handler.handle(*this); } +runtime::RunIfWaitingForDebuggerRequest::RunIfWaitingForDebuggerRequest() + : Request("Runtime.runIfWaitingForDebugger") {} + +runtime::RunIfWaitingForDebuggerRequest::RunIfWaitingForDebuggerRequest( + const dynamic &obj) + : Request("Runtime.runIfWaitingForDebugger") { + assign(id, obj, "id"); + assign(method, obj, "method"); +} + +dynamic runtime::RunIfWaitingForDebuggerRequest::toDynamic() const { + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "method", method); + return obj; +} + +void runtime::RunIfWaitingForDebuggerRequest::accept( + RequestHandler &handler) const { + handler.handle(*this); +} + /// Responses ErrorResponse::ErrorResponse(const dynamic &obj) { assign(id, obj, "id"); diff --git a/ReactCommon/hermes/inspector/chrome/MessageTypes.h b/ReactCommon/hermes/inspector/chrome/MessageTypes.h index 931e19fb41ab29..d7e38d81a49d13 100644 --- a/ReactCommon/hermes/inspector/chrome/MessageTypes.h +++ b/ReactCommon/hermes/inspector/chrome/MessageTypes.h @@ -1,5 +1,5 @@ // Copyright 2004-present Facebook. All Rights Reserved. -// @generated SignedSource<> +// @generated SignedSource<<356df52df2a053b5254f0e039cc36a7b>> #pragma once @@ -61,6 +61,7 @@ struct InternalPropertyDescriptor; struct PropertyDescriptor; struct RemoteObject; using RemoteObjectId = std::string; +struct RunIfWaitingForDebuggerRequest; using ScriptId = std::string; struct StackTrace; using Timestamp = double; @@ -101,6 +102,7 @@ struct RequestHandler { virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0; virtual void handle(const runtime::EvaluateRequest &req) = 0; virtual void handle(const runtime::GetPropertiesRequest &req) = 0; + virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0; }; /// NoopRequestHandler can be subclassed to only handle some requests. @@ -127,6 +129,7 @@ struct NoopRequestHandler : public RequestHandler { void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {} void handle(const runtime::EvaluateRequest &req) override {} void handle(const runtime::GetPropertiesRequest &req) override {} + void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {} }; /// Types @@ -455,6 +458,14 @@ struct runtime::GetPropertiesRequest : public Request { folly::Optional ownProperties; }; +struct runtime::RunIfWaitingForDebuggerRequest : public Request { + RunIfWaitingForDebuggerRequest(); + explicit RunIfWaitingForDebuggerRequest(const folly::dynamic &obj); + + folly::dynamic toDynamic() const override; + void accept(RequestHandler &handler) const override; +}; + /// Responses struct ErrorResponse : public Response { ErrorResponse() = default; diff --git a/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp b/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp index b55194a1869e1e..9d5abc5962a413 100644 --- a/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp +++ b/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp @@ -44,7 +44,8 @@ namespace { // the already-deallocated connection. class TestContext { public: - TestContext() : conn_(runtime_.runtime()) {} + TestContext(bool waitForDebugger = false) + : conn_(runtime_.runtime(), waitForDebugger) {} ~TestContext() { runtime_.wait(); } @@ -85,6 +86,7 @@ NotificationType expectNotification(SyncConnection &conn) { note = NotificationType(folly::parseJson(str)); } catch (const std::exception &e) { parseError = e.what(); + parseError += " (json: " + str + ")"; } EXPECT_EQ(parseError, ""); @@ -2349,6 +2351,44 @@ TEST(ConnectionTests, wontStopOnFilesWithoutSourceMaps) { expectNotification(conn); } +TEST(ConnectionTests, runIfWaitingForDebugger) { + TestContext context(true); + AsyncHermesRuntime &asyncRuntime = context.runtime(); + SyncConnection &conn = context.conn(); + int msgId = 0; + + asyncRuntime.executeScriptAsync(R"( + storeValue(1); debugger; + )"); + + send(conn, ++msgId); + expectExecutionContextCreated(conn); + expectNotification(conn); + expectNotification(conn); + + // We should now be paused on load. Verify that we didn't run code. + ASSERT_FALSE(asyncRuntime.hasStoredValue()); + + // RunIfWaitingForDebugger should cause us to resume + send(conn, ++msgId); + expectNotification(conn); + + // We should immediately hit the 'debugger;' statement + expectNotification(conn); + EXPECT_EQ(1, asyncRuntime.awaitStoredValue().asNumber()); + + // RunIfWaitingForDebuggerResponse should be accepted but have no effect + send(conn, ++msgId); + + // Do a dummy call so we can expect something other than a ResumeRequest + sendRuntimeEvalRequest(conn, ++msgId, "true"); + expectEvalResponse(conn, msgId, true); + + // Finally explicitly continue and exit + send(conn, msgId++); + expectNotification(conn); +} + } // namespace chrome } // namespace inspector } // namespace hermes diff --git a/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.cpp b/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.cpp index d30b044d2167e7..0514db17fe9cef 100644 --- a/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.cpp +++ b/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.cpp @@ -52,12 +52,15 @@ class SyncConnection::RemoteConnnection : public IRemoteConnection { SyncConnection &conn_; }; -SyncConnection::SyncConnection(std::shared_ptr runtime) +SyncConnection::SyncConnection( + std::shared_ptr runtime, + bool waitForDebugger) : connection_( std::make_unique( runtime, runtime->getDebugger()), - "testConn") { + "testConn", + waitForDebugger) { connection_.connect(std::make_unique(*this)); } diff --git a/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.h b/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.h index 40f97ee7d0d6e8..97da2ec8657ba6 100644 --- a/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.h +++ b/ReactCommon/hermes/inspector/chrome/tests/SyncConnection.h @@ -28,7 +28,9 @@ namespace chrome { */ class SyncConnection { public: - SyncConnection(std::shared_ptr runtime); + SyncConnection( + std::shared_ptr runtime, + bool waitForDebugger = false); ~SyncConnection() = default; /// sends a message to the debugger diff --git a/ReactCommon/hermes/inspector/tools/message_types.txt b/ReactCommon/hermes/inspector/tools/message_types.txt index 0db4a6b7709ce9..02efcce5f1bc50 100644 --- a/ReactCommon/hermes/inspector/tools/message_types.txt +++ b/ReactCommon/hermes/inspector/tools/message_types.txt @@ -24,3 +24,4 @@ Runtime.consoleAPICalled Runtime.evaluate Runtime.executionContextCreated Runtime.getProperties +Runtime.runIfWaitingForDebugger diff --git a/ReactCommon/jsi/BUCK b/ReactCommon/jsi/BUCK index f03406c20331e7..d4d1e6704575d1 100644 --- a/ReactCommon/jsi/BUCK +++ b/ReactCommon/jsi/BUCK @@ -35,6 +35,7 @@ rn_xplat_cxx_library( "-Wglobal-constructors", "-Wmissing-prototypes", ], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = ["PUBLIC"], ) @@ -52,6 +53,7 @@ rn_xplat_cxx_library( "-frtti", ], fbobjc_force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], @@ -78,9 +80,9 @@ rn_xplat_cxx_library( fbobjc_frameworks = [ "$SDKROOT/System/Library/Frameworks/JavaScriptCore.framework", ], - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], # TODO (T55502220): Remove when iOS 9.0 deprecation is complete everywhere. fbobjc_target_sdk_version = "9.0", + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = APPLE, visibility = ["PUBLIC"], xplat_mangled_args = { diff --git a/ReactCommon/jsi/JSCRuntime.cpp b/ReactCommon/jsi/JSCRuntime.cpp index 91f87b81f1cf7b..b6e6062847cc87 100644 --- a/ReactCommon/jsi/JSCRuntime.cpp +++ b/ReactCommon/jsi/JSCRuntime.cpp @@ -185,7 +185,7 @@ class JSCRuntime : public jsi::Runtime { // TODO: revisit this implementation jsi::WeakObject createWeakObject(const jsi::Object &) override; - jsi::Value lockWeakObject(const jsi::WeakObject &) override; + jsi::Value lockWeakObject(jsi::WeakObject &) override; jsi::Array createArray(size_t length) override; size_t size(const jsi::Array &) override; @@ -988,7 +988,7 @@ jsi::WeakObject JSCRuntime::createWeakObject(const jsi::Object &obj) { #endif } -jsi::Value JSCRuntime::lockWeakObject(const jsi::WeakObject &obj) { +jsi::Value JSCRuntime::lockWeakObject(jsi::WeakObject &obj) { #ifdef RN_FABRIC_ENABLED // TODO: revisit this implementation JSObjectRef objRef = objectRef(obj); diff --git a/ReactCommon/jsi/jsi/decorator.h b/ReactCommon/jsi/jsi/decorator.h index d090a47fc9c974..46e7414abbecb1 100644 --- a/ReactCommon/jsi/jsi/decorator.h +++ b/ReactCommon/jsi/jsi/decorator.h @@ -256,7 +256,7 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { WeakObject createWeakObject(const Object& o) override { return plain_.createWeakObject(o); }; - Value lockWeakObject(const WeakObject& wo) override { + Value lockWeakObject(WeakObject& wo) override { return plain_.lockWeakObject(wo); }; @@ -633,7 +633,7 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around around{with_}; return RD::createWeakObject(o); }; - Value lockWeakObject(const WeakObject& wo) override { + Value lockWeakObject(WeakObject& wo) override { Around around{with_}; return RD::lockWeakObject(wo); }; diff --git a/ReactCommon/jsi/jsi/jsi-inl.h b/ReactCommon/jsi/jsi/jsi-inl.h index 08495995663c7c..63d4a2f5868d90 100644 --- a/ReactCommon/jsi/jsi/jsi-inl.h +++ b/ReactCommon/jsi/jsi/jsi-inl.h @@ -65,6 +65,10 @@ inline T Runtime::make(Runtime::PointerValue* pv) { return T(pv); } +inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) { + return pointer.ptr_; +} + inline const Runtime::PointerValue* Runtime::getPointerValue( const jsi::Pointer& pointer) { return pointer.ptr_; diff --git a/ReactCommon/jsi/jsi/jsi.h b/ReactCommon/jsi/jsi/jsi.h index e48f524a105b1d..67823488442ab8 100644 --- a/ReactCommon/jsi/jsi/jsi.h +++ b/ReactCommon/jsi/jsi/jsi.h @@ -280,7 +280,7 @@ class Runtime { virtual Array getPropertyNames(const Object&) = 0; virtual WeakObject createWeakObject(const Object&) = 0; - virtual Value lockWeakObject(const WeakObject&) = 0; + virtual Value lockWeakObject(WeakObject&) = 0; virtual Array createArray(size_t length) = 0; virtual size_t size(const Array&) = 0; @@ -316,6 +316,7 @@ class Runtime { // Value, Symbol, String, and Object, which are all friends of Runtime. template static T make(PointerValue* pv); + static PointerValue* getPointerValue(Pointer& pointer); static const PointerValue* getPointerValue(const Pointer& pointer); static const PointerValue* getPointerValue(const Value& value); diff --git a/ReactCommon/jsiexecutor/Android.mk b/ReactCommon/jsiexecutor/Android.mk index c6bb60124a0744..74ae7a65cef20e 100644 --- a/ReactCommon/jsiexecutor/Android.mk +++ b/ReactCommon/jsiexecutor/Android.mk @@ -16,7 +16,7 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) LOCAL_CFLAGS := -fexceptions -frtti -O3 -LOCAL_STATIC_LIBRARIES := libjsi reactnative +LOCAL_STATIC_LIBRARIES := libjsi reactnative reactperflogger LOCAL_SHARED_LIBRARIES := libfolly_json glog include $(BUILD_STATIC_LIBRARY) diff --git a/ReactCommon/jsiexecutor/BUCK b/ReactCommon/jsiexecutor/BUCK index 1edfe366a9ac7e..ab3a5f50d5a68c 100644 --- a/ReactCommon/jsiexecutor/BUCK +++ b/ReactCommon/jsiexecutor/BUCK @@ -20,10 +20,9 @@ cxx_library( "//xplat/folly:molly", "//xplat/third-party/linker_lib:atomic", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_force_static = True, fbobjc_header_path_prefix = "", - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", @@ -39,5 +38,6 @@ cxx_library( react_native_xplat_dep("jsi:JSIDynamic"), react_native_xplat_target("cxxreact:bridge"), react_native_xplat_target("cxxreact:jsbigstring"), + react_native_xplat_target("reactperflogger:reactperflogger"), ], ) diff --git a/ReactCommon/jsiexecutor/React-jsiexecutor.podspec b/ReactCommon/jsiexecutor/React-jsiexecutor.podspec index eef1279ac5e33f..a8ac598cbf85bd 100644 --- a/ReactCommon/jsiexecutor/React-jsiexecutor.podspec +++ b/ReactCommon/jsiexecutor/React-jsiexecutor.podspec @@ -37,6 +37,7 @@ Pod::Spec.new do |s| s.dependency "React-cxxreact", version s.dependency "React-jsi", version s.dependency "RCT-Folly", folly_version + s.dependency "React-perflogger", version s.dependency "DoubleConversion" s.dependency "glog" end diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp index fd77eba63e6461..c305615250b442 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp +++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -71,6 +72,7 @@ JSIExecutor::JSIExecutor( delegate_(delegate), nativeModules_(std::make_shared( delegate ? delegate->getModuleRegistry() : nullptr)), + moduleRegistry_(delegate ? delegate->getModuleRegistry() : nullptr), scopedTimeoutInvoker_(scopedTimeoutInvoker), runtimeInstaller_(runtimeInstaller) { runtime_->global().setProperty( @@ -383,6 +385,8 @@ void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) { .getPropertyAsFunction(*runtime_, "stringify").call(*runtime_, queue) .getString(*runtime_).utf8(*runtime_); #endif + BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessStart(); + delegate_->callNativeModules( *this, dynamicFromValue(*runtime_, queue), isEndOfBatch); } @@ -440,16 +444,54 @@ Value JSIExecutor::nativeCallSyncHook(const Value *args, size_t count) { folly::to("method parameters should be array")); } + unsigned int moduleId = static_cast(args[0].getNumber()); + unsigned int methodId = static_cast(args[1].getNumber()); + std::string moduleName; + std::string methodName; + + if (moduleRegistry_) { + moduleName = moduleRegistry_->getModuleName(moduleId); + methodName = moduleRegistry_->getModuleSyncMethodName(moduleId, methodId); + + BridgeNativeModulePerfLogger::syncMethodCallStart( + moduleName.c_str(), methodName.c_str()); + + BridgeNativeModulePerfLogger::syncMethodCallArgConversionStart( + moduleName.c_str(), methodName.c_str()); + } + MethodCallResult result = delegate_->callSerializableNativeHook( - *this, - static_cast(args[0].getNumber()), // moduleId - static_cast(args[1].getNumber()), // methodId - dynamicFromValue(*runtime_, args[2])); // args + *this, moduleId, methodId, dynamicFromValue(*runtime_, args[2])); + + /** + * Note: + * In RCTNativeModule, folly::none is returned from callSerializableNativeHook + * when executing a NativeModule method fails. Therefore, it's safe to not + * terminate the syncMethodCall when folly::none is returned. + * + * TODO: In JavaNativeModule, folly::none is returned when the synchronous + * NativeModule method has the void return type. Change this to return + * folly::dynamic(nullptr) instead, so that folly::none is reserved for + * exceptional scenarios. + * + * TODO: Investigate CxxModule infra to see if folly::none is used for + * returns in exceptional scenarios. + **/ if (!result.hasValue()) { return Value::undefined(); } - return valueFromDynamic(*runtime_, result.value()); + + Value returnValue = valueFromDynamic(*runtime_, result.value()); + + if (moduleRegistry_) { + BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd( + moduleName.c_str(), methodName.c_str()); + BridgeNativeModulePerfLogger::syncMethodCallEnd( + moduleName.c_str(), methodName.c_str()); + } + + return returnValue; } #if DEBUG diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h index 2cd3776baf52a2..615172ed711c64 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h +++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h @@ -123,6 +123,7 @@ class JSIExecutor : public JSExecutor { std::shared_ptr runtime_; std::shared_ptr delegate_; std::shared_ptr nativeModules_; + std::shared_ptr moduleRegistry_; std::once_flag bindFlag_; std::unique_ptr bundleRegistry_; JSIScopedTimeoutInvoker scopedTimeoutInvoker_; diff --git a/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp b/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp index 425152cdac688d..e7c56c206a5f26 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp +++ b/ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp @@ -6,6 +6,7 @@ */ #include "jsireact/JSINativeModules.h" +#include #include @@ -31,13 +32,21 @@ Value JSINativeModules::getModule(Runtime &rt, const PropNameID &name) { std::string moduleName = name.utf8(rt); + BridgeNativeModulePerfLogger::moduleJSRequireBeginningStart( + moduleName.c_str()); + const auto it = m_objects.find(moduleName); if (it != m_objects.end()) { + BridgeNativeModulePerfLogger::moduleJSRequireBeginningCacheHit( + moduleName.c_str()); + BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd( + moduleName.c_str()); return Value(rt, it->second); } auto module = createModule(rt, moduleName); if (!module.hasValue()) { + BridgeNativeModulePerfLogger::moduleJSRequireEndingFail(moduleName.c_str()); // Allow lookup to continue in the objects own properties, which allows for // overrides of NativeModules return nullptr; @@ -45,7 +54,10 @@ Value JSINativeModules::getModule(Runtime &rt, const PropNameID &name) { auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first; - return Value(rt, result->second); + + Value ret = Value(rt, result->second); + BridgeNativeModulePerfLogger::moduleJSRequireEndingEnd(moduleName.c_str()); + return ret; } void JSINativeModules::reset() { diff --git a/ReactCommon/jsinspector/BUCK b/ReactCommon/jsinspector/BUCK index 22b569eabee601..a64c399b567d72 100644 --- a/ReactCommon/jsinspector/BUCK +++ b/ReactCommon/jsinspector/BUCK @@ -30,9 +30,8 @@ rn_xplat_cxx_library( "-fexceptions", "-std=c++1y", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_preferred_linkage = "shared", - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactCommon/microprofiler/BUCK b/ReactCommon/microprofiler/BUCK index 571b3cea525be1..d01a92546fc4ac 100644 --- a/ReactCommon/microprofiler/BUCK +++ b/ReactCommon/microprofiler/BUCK @@ -16,9 +16,8 @@ cxx_library( "-fexceptions", "-fno-data-sections", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], visibility = [ "PUBLIC", ], diff --git a/ReactCommon/reactperflogger/.clang-tidy b/ReactCommon/reactperflogger/.clang-tidy new file mode 100644 index 00000000000000..c98fd78ff64baa --- /dev/null +++ b/ReactCommon/reactperflogger/.clang-tidy @@ -0,0 +1,5 @@ +--- +Checks: '> +clang-diagnostic-*, +' +... diff --git a/ReactCommon/reactperflogger/Android.mk b/ReactCommon/reactperflogger/Android.mk new file mode 100644 index 00000000000000..f2c15a8027e325 --- /dev/null +++ b/ReactCommon/reactperflogger/Android.mk @@ -0,0 +1,25 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Header search path for all source files in this module. +LOCAL_C_INCLUDES := $(LOCAL_PATH)/reactperflogger + +# Header search path for modules that depend on this module +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall + +# Name of this module. +LOCAL_MODULE := reactperflogger + +# Compile all local c++ files under ./ReactCommon +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/reactperflogger/*.cpp) + +# Build the files in this directory as a shared library +include $(BUILD_STATIC_LIBRARY) diff --git a/ReactCommon/reactperflogger/BUCK b/ReactCommon/reactperflogger/BUCK new file mode 100644 index 00000000000000..9724f144272352 --- /dev/null +++ b/ReactCommon/reactperflogger/BUCK @@ -0,0 +1,28 @@ +load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "rn_xplat_cxx_library") + +rn_xplat_cxx_library( + name = "reactperflogger", + srcs = glob(["**/*.cpp"]), + header_namespace = "", + exported_headers = { + "reactperflogger/BridgeNativeModulePerfLogger.h": "reactperflogger/BridgeNativeModulePerfLogger.h", + "reactperflogger/NativeModulePerfLogger.h": "reactperflogger/NativeModulePerfLogger.h", + }, + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + "-Wno-global-constructors", + ], + labels = ["supermodule:xplat/default/public.react_native.infra"], + platforms = (ANDROID, APPLE), + preferred_linkage = "static", + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + visibility = [ + "PUBLIC", + ], +) diff --git a/ReactCommon/reactperflogger/React-perflogger.podspec b/ReactCommon/reactperflogger/React-perflogger.podspec new file mode 100644 index 00000000000000..2bb69eb70c605f --- /dev/null +++ b/ReactCommon/reactperflogger/React-perflogger.podspec @@ -0,0 +1,34 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip +else + source[:tag] = "v#{version}" +end + +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' +folly_version = '2020.01.13.00' +boost_compiler_flags = '-Wno-documentation' + +Pod::Spec.new do |s| + s.name = "React-perflogger" + s.version = version + s.summary = "-" # TODO + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Facebook, Inc. and its affiliates" + s.platforms = { :ios => "10.0", :tvos => "10.0", :osx => "10.14" } # TODO(macOS GH#774) + s.source = source + s.source_files = "**/*.{cpp,h}" + s.header_dir = "reactperflogger" +end diff --git a/ReactCommon/reactperflogger/reactperflogger/BridgeNativeModulePerfLogger.cpp b/ReactCommon/reactperflogger/reactperflogger/BridgeNativeModulePerfLogger.cpp new file mode 100644 index 00000000000000..70582331c799d8 --- /dev/null +++ b/ReactCommon/reactperflogger/reactperflogger/BridgeNativeModulePerfLogger.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "BridgeNativeModulePerfLogger.h" + +namespace facebook { +namespace react { +namespace BridgeNativeModulePerfLogger { + +std::unique_ptr g_perfLogger = nullptr; + +void enableLogging(std::unique_ptr &&newPerfLogger) { + g_perfLogger = std::move(newPerfLogger); +} + +void moduleDataCreateStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleDataCreateStart(moduleName, id); + } +} + +void moduleDataCreateEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleDataCreateEnd(moduleName, id); + } +} + +void moduleCreateStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateStart(moduleName, id); + } +} + +void moduleCreateCacheHit(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateCacheHit(moduleName, id); + } +} + +void moduleCreateConstructStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateConstructStart(moduleName, id); + } +} + +void moduleCreateConstructEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateConstructEnd(moduleName, id); + } +} + +void moduleCreateSetUpStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateSetUpStart(moduleName, id); + } +} + +void moduleCreateSetUpEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateSetUpEnd(moduleName, id); + } +} + +void moduleCreateEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateEnd(moduleName, id); + } +} + +void moduleCreateFail(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateFail(moduleName, id); + } +} + +void moduleJSRequireBeginningStart(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningStart(moduleName); + } +} + +void moduleJSRequireBeginningCacheHit(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningCacheHit(moduleName); + } +} + +void moduleJSRequireBeginningEnd(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningEnd(moduleName); + } +} + +void moduleJSRequireBeginningFail(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningFail(moduleName); + } +} + +void moduleJSRequireEndingStart(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireEndingStart(moduleName); + } +} + +void moduleJSRequireEndingEnd(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireEndingEnd(moduleName); + } +} + +void moduleJSRequireEndingFail(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireEndingFail(moduleName); + } +} + +void syncMethodCallStart(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallStart(moduleName, methodName); + } +} + +void syncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallArgConversionStart(moduleName, methodName); + } +} + +void syncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallArgConversionEnd(moduleName, methodName); + } +} + +void syncMethodCallExecutionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallExecutionStart(moduleName, methodName); + } +} +void syncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallExecutionEnd(moduleName, methodName); + } +} + +void syncMethodCallReturnConversionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallReturnConversionStart(moduleName, methodName); + } +} + +void syncMethodCallReturnConversionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallReturnConversionEnd(moduleName, methodName); + } +} + +void syncMethodCallEnd(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallEnd(moduleName, methodName); + } +} + +void syncMethodCallFail(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallFail(moduleName, methodName); + } +} + +void asyncMethodCallStart(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallStart(moduleName, methodName); + } +} + +void asyncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallArgConversionStart(moduleName, methodName); + } +} + +void asyncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallArgConversionEnd(moduleName, methodName); + } +} + +void asyncMethodCallDispatch(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallDispatch(moduleName, methodName); + } +} + +void asyncMethodCallEnd(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallEnd(moduleName, methodName); + } +} + +void asyncMethodCallBatchPreprocessStart() { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallBatchPreprocessStart(); + } +} + +void asyncMethodCallBatchPreprocessEnd(int batchSize) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallBatchPreprocessEnd(batchSize); + } +} + +void asyncMethodCallExecutionStart( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionStart(moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionArgConversionStart( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionArgConversionStart( + moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionArgConversionEnd( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionArgConversionEnd( + moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionEnd(moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionFail( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionFail(moduleName, methodName, id); + } +} + +} // namespace BridgeNativeModulePerfLogger +} // namespace react +} // namespace facebook diff --git a/ReactCommon/reactperflogger/reactperflogger/BridgeNativeModulePerfLogger.h b/ReactCommon/reactperflogger/reactperflogger/BridgeNativeModulePerfLogger.h new file mode 100644 index 00000000000000..e82257abf538f6 --- /dev/null +++ b/ReactCommon/reactperflogger/reactperflogger/BridgeNativeModulePerfLogger.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include +#include "NativeModulePerfLogger.h" + +namespace facebook { +namespace react { + +namespace BridgeNativeModulePerfLogger { +void enableLogging(std::unique_ptr &&logger); + +void moduleDataCreateStart(const char *moduleName, int32_t id); +void moduleDataCreateEnd(const char *moduleName, int32_t id); + +/** + * Create NativeModule platform object + */ +void moduleCreateStart(const char *moduleName, int32_t id); +void moduleCreateCacheHit(const char *moduleName, int32_t id); +void moduleCreateConstructStart(const char *moduleName, int32_t id); +void moduleCreateConstructEnd(const char *moduleName, int32_t id); +void moduleCreateSetUpStart(const char *moduleName, int32_t id); +void moduleCreateSetUpEnd(const char *moduleName, int32_t id); +void moduleCreateEnd(const char *moduleName, int32_t id); +void moduleCreateFail(const char *moduleName, int32_t id); + +/** + * JS require beginning + */ +void moduleJSRequireBeginningStart(const char *moduleName); +void moduleJSRequireBeginningCacheHit(const char *moduleName); +void moduleJSRequireBeginningEnd(const char *moduleName); +void moduleJSRequireBeginningFail(const char *moduleName); + +/** + * JS require ending + */ +void moduleJSRequireEndingStart(const char *moduleName); +void moduleJSRequireEndingEnd(const char *moduleName); +void moduleJSRequireEndingFail(const char *moduleName); + +// Sync method calls +void syncMethodCallStart(const char *moduleName, const char *methodName); +void syncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName); +void syncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName); +void syncMethodCallExecutionStart( + const char *moduleName, + const char *methodName); +void syncMethodCallExecutionEnd(const char *moduleName, const char *methodName); +void syncMethodCallReturnConversionStart( + const char *moduleName, + const char *methodName); +void syncMethodCallReturnConversionEnd( + const char *moduleName, + const char *methodName); +void syncMethodCallEnd(const char *moduleName, const char *methodName); +void syncMethodCallFail(const char *moduleName, const char *methodName); + +// Async method calls +void asyncMethodCallStart(const char *moduleName, const char *methodName); +void asyncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName); +void asyncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName); +void asyncMethodCallDispatch(const char *moduleName, const char *methodName); +void asyncMethodCallEnd(const char *moduleName, const char *methodName); + +/** + * Pre-processing async method call batch + */ +void asyncMethodCallBatchPreprocessStart(); +void asyncMethodCallBatchPreprocessEnd(int batchSize); + +// Async method call execution +void asyncMethodCallExecutionStart( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionArgConversionStart( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionArgConversionEnd( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionFail( + const char *moduleName, + const char *methodName, + int32_t id); + +} // namespace BridgeNativeModulePerfLogger + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/reactperflogger/reactperflogger/NativeModulePerfLogger.h b/ReactCommon/reactperflogger/reactperflogger/NativeModulePerfLogger.h new file mode 100644 index 00000000000000..f4e9591e0c439f --- /dev/null +++ b/ReactCommon/reactperflogger/reactperflogger/NativeModulePerfLogger.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include + +namespace facebook { +namespace react { + +/** + * A platform-agnostic interface to do performance logging on NativeModules and + * TuboModules. + */ +class NativeModulePerfLogger { + public: + virtual ~NativeModulePerfLogger() {} + + /** + * NativeModule Initialization. + * + * The initialization of two NativeModules can interleave. Therefore, + * performance markers should use the moduleName as a unique key. + */ + + /** + * On iOS: + * - NativeModule initialization is split into two phases, which sometimes + * have a pause in the middle. + * - TurboModule initialization happens all at once. + * + * On Android: + * - NativeModule and TurboModule initialization happens all at once. + * + * These markers are meant for iOS NativeModules: + * - moduleDataCreateStart: very beginning of first phase. + * - moduleDataCreateEnd: after RCTModuleData has been created. + */ + virtual void moduleDataCreateStart(const char *moduleName, int32_t id) = 0; + virtual void moduleDataCreateEnd(const char *moduleName, int32_t id) = 0; + + /** + * How long does it take to create the platform NativeModule object? + * - moduleCreateStart: start creating platform NativeModule + * - moduleCreateEnd: stop creating platform NativeModule + */ + virtual void moduleCreateStart(const char *moduleName, int32_t id) = 0; + virtual void moduleCreateCacheHit(const char *moduleName, int32_t id) = 0; + virtual void moduleCreateConstructStart( + const char *moduleName, + int32_t id) = 0; + virtual void moduleCreateConstructEnd(const char *moduleName, int32_t id) = 0; + virtual void moduleCreateSetUpStart(const char *moduleName, int32_t id) = 0; + virtual void moduleCreateSetUpEnd(const char *moduleName, int32_t id) = 0; + virtual void moduleCreateEnd(const char *moduleName, int32_t id) = 0; + virtual void moduleCreateFail(const char *moduleName, int32_t id) = 0; + + /** + * How long, after starting JS require, does it take to start creating the + * platform NativeModule? + * - moduleJSRequireBeginningStart: start of JS require + * - moduleJSRequireBeginningEnd: start creating platform NativeModule + */ + virtual void moduleJSRequireBeginningStart(const char *moduleName) = 0; + virtual void moduleJSRequireBeginningCacheHit(const char *moduleName) = 0; + virtual void moduleJSRequireBeginningEnd(const char *moduleName) = 0; + virtual void moduleJSRequireBeginningFail(const char *moduleName) = 0; + + /** + * How long does it take to return from the JS require after the platform + * NativeModule is created? + * - moduleJSRequireEndingStart: end creating platform NativeModule + * - moduleJSRequireEndingEnd: end of JS require + */ + virtual void moduleJSRequireEndingStart(const char *moduleName) = 0; + virtual void moduleJSRequireEndingEnd(const char *moduleName) = 0; + virtual void moduleJSRequireEndingFail(const char *moduleName) = 0; + + // Sync method calls + virtual void syncMethodCallStart( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallExecutionStart( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallReturnConversionStart( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallReturnConversionEnd( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallEnd( + const char *moduleName, + const char *methodName) = 0; + virtual void syncMethodCallFail( + const char *moduleName, + const char *methodName) = 0; + + // Async method calls + virtual void asyncMethodCallStart( + const char *moduleName, + const char *methodName) = 0; + virtual void asyncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName) = 0; + virtual void asyncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName) = 0; + virtual void asyncMethodCallDispatch( + const char *moduleName, + const char *methodName) = 0; + virtual void asyncMethodCallEnd( + const char *moduleName, + const char *methodName) = 0; + + /** + * In the NativeModule system, we batch async NativeModule method calls. + * When we execute a batch of NativeModule method calls, we convert the batch + * from a jsi::Value to folly::dynamic to std::vector. This marker + * documents that work. + */ + virtual void asyncMethodCallBatchPreprocessStart() = 0; + virtual void asyncMethodCallBatchPreprocessEnd(int batchSize) = 0; + + // Async method call execution + virtual void asyncMethodCallExecutionStart( + const char *moduleName, + const char *methodName, + int32_t id) = 0; + virtual void asyncMethodCallExecutionArgConversionStart( + const char *moduleName, + const char *methodName, + int32_t id) = 0; + virtual void asyncMethodCallExecutionArgConversionEnd( + const char *moduleName, + const char *methodName, + int32_t id) = 0; + virtual void asyncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName, + int32_t id) = 0; + virtual void asyncMethodCallExecutionFail( + const char *moduleName, + const char *methodName, + int32_t id) = 0; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/runtimeexecutor/BUCK b/ReactCommon/runtimeexecutor/BUCK index 561b4c8d208b49..08ee7f4c232722 100644 --- a/ReactCommon/runtimeexecutor/BUCK +++ b/ReactCommon/runtimeexecutor/BUCK @@ -36,9 +36,9 @@ rn_xplat_cxx_library( ], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, fbobjc_frameworks = ["Foundation"], - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h b/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h index e01e2df41c1da8..90139a6a99cfea 100644 --- a/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h +++ b/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h @@ -48,7 +48,7 @@ inline static void executeAsynchronously( * Executes a `callback` in a *synchronous* manner using given * `RuntimeExecutor`. * Use this method when the caller needs to *be blocked* by executing the - * callback but does not concerted about the particular thread on which the + * callback but does not concerned about the particular thread on which the * `callback` will be executed. */ inline static void executeSynchronously_CAN_DEADLOCK( @@ -89,8 +89,18 @@ inline static void executeSynchronouslyOnSameThread_CAN_DEADLOCK( jsi::Runtime *runtimePtr; + auto threadId = std::this_thread::get_id(); + runtimeExecutor([&](jsi::Runtime &runtime) { runtimePtr = &runtime; + + if (threadId == std::this_thread::get_id()) { + // In case of a synchronous call, we should unlock mutexes and return. + mutex1.unlock(); + mutex3.unlock(); + return; + } + mutex1.unlock(); // `callback` is called somewhere here. mutex2.lock(); diff --git a/ReactCommon/turbomodule/core/BUCK b/ReactCommon/turbomodule/core/BUCK index af7350e7f2ccad..512c16ef811fe2 100644 --- a/ReactCommon/turbomodule/core/BUCK +++ b/ReactCommon/turbomodule/core/BUCK @@ -18,6 +18,7 @@ rn_xplat_cxx_library( "-frtti", "-std=c++14", "-Wall", + "-Wno-global-constructors", ], fbandroid_deps = [ react_native_target("jni/react/jni:jni"), @@ -28,7 +29,6 @@ rn_xplat_cxx_library( ], prefix = "ReactCommon", ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( [ "platform/android/**/*.cpp", @@ -39,7 +39,6 @@ rn_xplat_cxx_library( "-fobjc-arc-exceptions", ], fbobjc_inherited_buck_flags = get_static_library_ios_flags(), - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = OBJC_ARC_PREPROCESSOR_FLAGS + get_preprocessor_flags_for_build_mode(), force_static = True, ios_deps = [ @@ -62,6 +61,7 @@ rn_xplat_cxx_library( "platform/ios/**/*.mm", ], ), + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", @@ -80,6 +80,7 @@ rn_xplat_cxx_library( react_native_xplat_target("cxxreact:bridge"), react_native_xplat_target("cxxreact:module"), react_native_xplat_target("callinvoker:callinvoker"), + react_native_xplat_target("reactperflogger:reactperflogger"), ], exported_deps = [ "//xplat/jsi:jsi", diff --git a/ReactCommon/turbomodule/core/TurboModulePerfLogger.cpp b/ReactCommon/turbomodule/core/TurboModulePerfLogger.cpp new file mode 100644 index 00000000000000..5d145e1b869b39 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModulePerfLogger.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TurboModulePerfLogger.h" + +namespace facebook { +namespace react { +namespace TurboModulePerfLogger { + +std::unique_ptr g_perfLogger = nullptr; + +void enableLogging(std::unique_ptr &&newPerfLogger) { + g_perfLogger = std::move(newPerfLogger); +} + +void moduleDataCreateStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleDataCreateStart(moduleName, id); + } +} + +void moduleDataCreateEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleDataCreateEnd(moduleName, id); + } +} + +void moduleCreateStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateStart(moduleName, id); + } +} + +void moduleCreateCacheHit(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateCacheHit(moduleName, id); + } +} + +void moduleCreateConstructStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateConstructStart(moduleName, id); + } +} + +void moduleCreateConstructEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateConstructEnd(moduleName, id); + } +} + +void moduleCreateSetUpStart(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateSetUpStart(moduleName, id); + } +} + +void moduleCreateSetUpEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateSetUpEnd(moduleName, id); + } +} + +void moduleCreateEnd(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateEnd(moduleName, id); + } +} + +void moduleCreateFail(const char *moduleName, int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleCreateFail(moduleName, id); + } +} + +void moduleJSRequireBeginningStart(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningStart(moduleName); + } +} + +void moduleJSRequireBeginningCacheHit(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningCacheHit(moduleName); + } +} + +void moduleJSRequireBeginningEnd(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningEnd(moduleName); + } +} + +void moduleJSRequireBeginningFail(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireBeginningFail(moduleName); + } +} + +void moduleJSRequireEndingStart(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireEndingStart(moduleName); + } +} + +void moduleJSRequireEndingEnd(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireEndingEnd(moduleName); + } +} + +void moduleJSRequireEndingFail(const char *moduleName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->moduleJSRequireEndingFail(moduleName); + } +} + +void syncMethodCallStart(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallStart(moduleName, methodName); + } +} + +void syncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallArgConversionStart(moduleName, methodName); + } +} + +void syncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallArgConversionEnd(moduleName, methodName); + } +} + +void syncMethodCallExecutionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallExecutionStart(moduleName, methodName); + } +} +void syncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallExecutionEnd(moduleName, methodName); + } +} + +void syncMethodCallReturnConversionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallReturnConversionStart(moduleName, methodName); + } +} + +void syncMethodCallReturnConversionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallReturnConversionEnd(moduleName, methodName); + } +} + +void syncMethodCallEnd(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallEnd(moduleName, methodName); + } +} + +void syncMethodCallFail(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->syncMethodCallFail(moduleName, methodName); + } +} + +void asyncMethodCallStart(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallStart(moduleName, methodName); + } +} + +void asyncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallArgConversionStart(moduleName, methodName); + } +} + +void asyncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallArgConversionEnd(moduleName, methodName); + } +} + +void asyncMethodCallDispatch(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallDispatch(moduleName, methodName); + } +} + +void asyncMethodCallEnd(const char *moduleName, const char *methodName) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallEnd(moduleName, methodName); + } +} + +void asyncMethodCallBatchPreprocessStart() { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallBatchPreprocessStart(); + } +} + +void asyncMethodCallBatchPreprocessEnd(int batchSize) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallBatchPreprocessEnd(batchSize); + } +} + +void asyncMethodCallExecutionStart( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionStart(moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionArgConversionStart( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionArgConversionStart( + moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionArgConversionEnd( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionArgConversionEnd( + moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionEnd(moduleName, methodName, id); + } +} + +void asyncMethodCallExecutionFail( + const char *moduleName, + const char *methodName, + int32_t id) { + NativeModulePerfLogger *logger = g_perfLogger.get(); + if (logger != nullptr) { + logger->asyncMethodCallExecutionFail(moduleName, methodName, id); + } +} + +} // namespace TurboModulePerfLogger +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/TurboModulePerfLogger.h b/ReactCommon/turbomodule/core/TurboModulePerfLogger.h new file mode 100644 index 00000000000000..fe3a8ab30d4716 --- /dev/null +++ b/ReactCommon/turbomodule/core/TurboModulePerfLogger.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { +namespace TurboModulePerfLogger { +void enableLogging(std::unique_ptr &&logger); + +void moduleDataCreateStart(const char *moduleName, int32_t id); +void moduleDataCreateEnd(const char *moduleName, int32_t id); + +/** + * Create NativeModule platform object + */ +void moduleCreateStart(const char *moduleName, int32_t id); +void moduleCreateCacheHit(const char *moduleName, int32_t id); +void moduleCreateConstructStart(const char *moduleName, int32_t id); +void moduleCreateConstructEnd(const char *moduleName, int32_t id); +void moduleCreateSetUpStart(const char *moduleName, int32_t id); +void moduleCreateSetUpEnd(const char *moduleName, int32_t id); +void moduleCreateEnd(const char *moduleName, int32_t id); +void moduleCreateFail(const char *moduleName, int32_t id); + +/** + * JS require beginning + */ +void moduleJSRequireBeginningStart(const char *moduleName); +void moduleJSRequireBeginningCacheHit(const char *moduleName); +void moduleJSRequireBeginningEnd(const char *moduleName); +void moduleJSRequireBeginningFail(const char *moduleName); + +/** + * JS require ending + */ +void moduleJSRequireEndingStart(const char *moduleName); +void moduleJSRequireEndingEnd(const char *moduleName); +void moduleJSRequireEndingFail(const char *moduleName); + +// Sync method calls +void syncMethodCallStart(const char *moduleName, const char *methodName); +void syncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName); +void syncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName); +void syncMethodCallExecutionStart( + const char *moduleName, + const char *methodName); +void syncMethodCallExecutionEnd(const char *moduleName, const char *methodName); +void syncMethodCallReturnConversionStart( + const char *moduleName, + const char *methodName); +void syncMethodCallReturnConversionEnd( + const char *moduleName, + const char *methodName); +void syncMethodCallEnd(const char *moduleName, const char *methodName); +void syncMethodCallFail(const char *moduleName, const char *methodName); + +// Async method calls +void asyncMethodCallStart(const char *moduleName, const char *methodName); +void asyncMethodCallArgConversionStart( + const char *moduleName, + const char *methodName); +void asyncMethodCallArgConversionEnd( + const char *moduleName, + const char *methodName); +void asyncMethodCallDispatch(const char *moduleName, const char *methodName); +void asyncMethodCallEnd(const char *moduleName, const char *methodName); + +/** + * Pre-processing async method call batch + */ +void asyncMethodCallBatchPreprocessStart(); +void asyncMethodCallBatchPreprocessEnd(int batchSize); + +// Async method call execution +void asyncMethodCallExecutionStart( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionArgConversionStart( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionArgConversionEnd( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionEnd( + const char *moduleName, + const char *methodName, + int32_t id); +void asyncMethodCallExecutionFail( + const char *moduleName, + const char *methodName, + int32_t id); + +} // namespace TurboModulePerfLogger +} // namespace react +} // namespace facebook diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h index 89aa23832b8225..42dfd285928a49 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +#pragma once + #import #import @@ -22,101 +24,6 @@ ((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTTurboModule)])) #define RCT_IS_TURBO_MODULE_INSTANCE(module) RCT_IS_TURBO_MODULE_CLASS([(module) class]) -typedef int MethodCallId; - -/** - * This interface exists to allow the application to collect performance - * metrics of the TurboModule system. By implementing each function, you can - * hook into various stages of TurboModule creation and method dispatch (both async and sync). - * - * Note: - * - TurboModule async method invocations can interleave, so methodCallId should be used as a unique id for a method - * call. - */ -@protocol RCTTurboModulePerformanceLogger -// Create TurboModule JS Object -- (void)createTurboModuleStart:(const char *)moduleName; -- (void)createTurboModuleEnd:(const char *)moduleName; -- (void)createTurboModuleCacheHit:(const char *)moduleName; -- (void)getCppTurboModuleFromTMMDelegateStart:(const char *)moduleName; -- (void)getCppTurboModuleFromTMMDelegateEnd:(const char *)moduleName; -- (void)getTurboModuleFromRCTTurboModuleStart:(const char *)moduleName; -- (void)getTurboModuleFromRCTTurboModuleEnd:(const char *)moduleName; -- (void)getTurboModuleFromRCTCxxModuleStart:(const char *)moduleName; -- (void)getTurboModuleFromRCTCxxModuleEnd:(const char *)moduleName; -- (void)getTurboModuleFromTMMDelegateStart:(const char *)moduleName; -- (void)getTurboModuleFromTMMDelegateEnd:(const char *)moduleName; - -// Create RCTTurboModule object -- (void)createRCTTurboModuleStart:(const char *)moduleName; -- (void)createRCTTurboModuleEnd:(const char *)moduleName; -- (void)createRCTTurboModuleCacheHit:(const char *)moduleName; -- (void)getRCTTurboModuleClassStart:(const char *)moduleName; -- (void)getRCTTurboModuleClassEnd:(const char *)moduleName; -- (void)getRCTTurboModuleInstanceStart:(const char *)moduleName; -- (void)getRCTTurboModuleInstanceEnd:(const char *)moduleName; -- (void)setupRCTTurboModuleDispatch:(const char *)moduleName; -- (void)setupRCTTurboModuleStart:(const char *)moduleName; -- (void)setupRCTTurboModuleEnd:(const char *)moduleName; -- (void)attachRCTBridgeToRCTTurboModuleStart:(const char *)moduleName; -- (void)attachRCTBridgeToRCTTurboModuleEnd:(const char *)moduleName; -- (void)attachMethodQueueToRCTTurboModuleStart:(const char *)moduleName; -- (void)attachMethodQueueToRCTTurboModuleEnd:(const char *)moduleName; -- (void)registerRCTTurboModuleForFrameUpdatesStart:(const char *)moduleName; -- (void)registerRCTTurboModuleForFrameUpdatesEnd:(const char *)moduleName; -- (void)dispatchDidInitializeModuleNotificationForRCTTurboModuleStart:(const char *)moduleName; -- (void)dispatchDidInitializeModuleNotificationForRCTTurboModuleEnd:(const char *)moduleName; - -// Sync method invocation -- (void)syncMethodCallStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncMethodCallEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncMethodCallArgumentConversionStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncMethodCallArgumentConversionEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncRCTTurboModuleMethodCallStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncRCTTurboModuleMethodCallEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncMethodCallReturnConversionStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)syncMethodCallReturnConversionEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; - -// Async method invocation -- (void)asyncMethodCallStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)asyncMethodCallEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)asyncMethodCallArgumentConversionStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)asyncMethodCallArgumentConversionEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)asyncRCTTurboModuleMethodCallDispatch:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)asyncRCTTurboModuleMethodCallStart:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -- (void)asyncRCTTurboModuleMethodCallEnd:(const char *)moduleName - methodName:(const char *)methodName - methodCallId:(MethodCallId)methodCallId; -@end - namespace facebook { namespace react { @@ -133,7 +40,8 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { id instance; std::shared_ptr jsInvoker; std::shared_ptr nativeInvoker; - id perfLogger; + // Does the NativeModule dispatch async methods to the JS thread? + bool isSyncModule; }; ObjCTurboModule(const InitParams ¶ms); @@ -159,17 +67,9 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { */ NSMutableDictionary *methodArgConversionSelectors_; NSDictionary *> *methodArgumentTypeNames_; + const bool isSyncModule_; + bool isMethodSync(TurboModuleMethodValueKind returnType); NSString *getArgumentTypeName(NSString *methodName, int argIndex); - id performanceLogger_; - - /** - * Required for performance logging async method invocations. - * This field is static because two nth async method calls from different - * TurboModules can interleave, and should therefore be treated as two distinct calls. - */ - static MethodCallId methodCallId_; - - static MethodCallId getNewMethodCallId(); NSInvocation *getMethodInvocation( jsi::Runtime &runtime, @@ -178,15 +78,13 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { SEL selector, const jsi::Value *args, size_t count, - NSMutableArray *retainedObjectsForInvocation, - MethodCallId methodCallId); + NSMutableArray *retainedObjectsForInvocation); jsi::Value performMethodInvocation( jsi::Runtime &runtime, TurboModuleMethodValueKind returnType, const char *methodName, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation, - MethodCallId methodCallId); + NSMutableArray *retainedObjectsForInvocation); BOOL hasMethodArgConversionSelector(NSString *methodName, int argIndex); SEL getMethodArgConversionSelector(NSString *methodName, int argIndex); diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm index 221bff76829cb0..b44010d0304fce 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm @@ -9,6 +9,7 @@ #import #import +#import #import #import @@ -21,9 +22,16 @@ #import #import #import +#import #import -using namespace facebook; +using namespace facebook::react; + +static int32_t getUniqueId() +{ + static int32_t counter = 0; + return counter++; +} /** * All static helper functions are ObjC++ specific. @@ -90,17 +98,15 @@ return jsi::Value::undefined(); } -static id convertJSIValueToObjCObject( - jsi::Runtime &runtime, - const jsi::Value &value, - std::shared_ptr jsInvoker); +static id +convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker); static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value) { return [NSString stringWithUTF8String:value.utf8(runtime).c_str()]; } static NSArray * -convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr jsInvoker) +convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr jsInvoker) { size_t size = value.size(runtime); NSMutableArray *result = [NSMutableArray new]; @@ -112,10 +118,8 @@ static id convertJSIValueToObjCObject( return [result copy]; } -static NSDictionary *convertJSIObjectToNSDictionary( - jsi::Runtime &runtime, - const jsi::Object &value, - std::shared_ptr jsInvoker) +static NSDictionary * +convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr jsInvoker) { jsi::Array propertyNames = value.getPropertyNames(runtime); size_t size = propertyNames.size(runtime); @@ -131,14 +135,10 @@ static id convertJSIValueToObjCObject( return [result copy]; } -static RCTResponseSenderBlock convertJSIFunctionToCallback( - jsi::Runtime &runtime, - const jsi::Function &value, - std::shared_ptr jsInvoker); -static id convertJSIValueToObjCObject( - jsi::Runtime &runtime, - const jsi::Value &value, - std::shared_ptr jsInvoker) +static RCTResponseSenderBlock +convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr jsInvoker); +static id +convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr jsInvoker) { if (value.isUndefined() || value.isNull()) { return nil; @@ -166,12 +166,10 @@ static id convertJSIValueToObjCObject( throw std::runtime_error("Unsupported jsi::jsi::Value kind"); } -static RCTResponseSenderBlock convertJSIFunctionToCallback( - jsi::Runtime &runtime, - const jsi::Function &value, - std::shared_ptr jsInvoker) +static RCTResponseSenderBlock +convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr jsInvoker) { - auto weakWrapper = react::CallbackWrapper::createWeak(value.getFunction(runtime), runtime, jsInvoker); + auto weakWrapper = CallbackWrapper::createWeak(value.getFunction(runtime), runtime, jsInvoker); BOOL __block wrapperWasCalled = NO; return ^(NSArray *responses) { if (wrapperWasCalled) { @@ -203,7 +201,7 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( jsi::Value ObjCTurboModule::createPromise( jsi::Runtime &runtime, - std::shared_ptr jsInvoker, + std::shared_ptr jsInvoker, PromiseInvocationBlock invoke) { if (!invoke) { @@ -228,10 +226,8 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( return jsi::Value::undefined(); } - auto weakResolveWrapper = - react::CallbackWrapper::createWeak(args[0].getObject(rt).getFunction(rt), rt, jsInvoker); - auto weakRejectWrapper = - react::CallbackWrapper::createWeak(args[1].getObject(rt).getFunction(rt), rt, jsInvoker); + auto weakResolveWrapper = CallbackWrapper::createWeak(args[0].getObject(rt).getFunction(rt), rt, jsInvoker); + auto weakRejectWrapper = CallbackWrapper::createWeak(args[1].getObject(rt).getFunction(rt), rt, jsInvoker); __block BOOL resolveWasCalled = NO; __block BOOL rejectWasCalled = NO; @@ -324,15 +320,15 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( TurboModuleMethodValueKind returnType, const char *methodName, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation, - MethodCallId methodCallId) + NSMutableArray *retainedObjectsForInvocation) { __block id result; jsi::Runtime *rt = &runtime; __weak id weakModule = instance_; - id performanceLogger = performanceLogger_; const char *moduleName = name_.c_str(); - const bool isSync = returnType != VoidKind && returnType != PromiseKind; + std::string methodNameStr{methodName}; + __block int32_t asyncCallCounter = 0; + bool wasMethodSync = isMethodSync(returnType); void (^block)() = ^{ if (!weakModule) { @@ -341,40 +337,43 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( id strongModule = weakModule; - if (isSync) { - [performanceLogger syncRCTTurboModuleMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId]; + if (wasMethodSync) { + TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodNameStr.c_str()); } else { - [performanceLogger asyncRCTTurboModuleMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId]; + TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter); } + // TODO(T66699874) Should we guard this with a try/catch? [inv invokeWithTarget:strongModule]; [retainedObjectsForInvocation removeAllObjects]; - if (returnType == VoidKind) { - [performanceLogger asyncRCTTurboModuleMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId]; + if (!wasMethodSync) { + TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter); return; } + + TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodNameStr.c_str()); + TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodNameStr.c_str()); + void *rawResult; [inv getReturnValue:&rawResult]; result = (__bridge id)rawResult; - [performanceLogger syncRCTTurboModuleMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId]; }; - if (returnType == VoidKind) { - nativeInvoker_->invokeAsync([block]() -> void { block(); }); - } else { + if (wasMethodSync) { nativeInvoker_->invokeSync([block]() -> void { block(); }); + } else { + asyncCallCounter = getUniqueId(); + TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName); + nativeInvoker_->invokeAsync([block]() -> void { block(); }); + return jsi::Value::undefined(); } - // VoidKind can't be null - // PromiseKind, and FunctionKind must throw errors always - if (returnType != VoidKind && returnType != PromiseKind && returnType != FunctionKind && - (result == (id)kCFNull || result == nil)) { + if (result == (id)kCFNull || result == nil) { return jsi::Value::null(); } jsi::Value returnValue = jsi::Value::undefined(); - [performanceLogger_ syncMethodCallReturnConversionStart:moduleName methodName:methodName methodCallId:methodCallId]; // TODO: Re-use value conversion logic from existing impl, if possible. switch (returnType) { @@ -407,7 +406,7 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( throw std::runtime_error("convertInvocationResultToJSIValue: PromiseKind wasn't handled properly."); } - [performanceLogger_ syncMethodCallReturnConversionEnd:moduleName methodName:methodName methodCallId:methodCallId]; + TurboModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName); return returnValue; } @@ -476,21 +475,15 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( SEL selector, const jsi::Value *args, size_t count, - NSMutableArray *retainedObjectsForInvocation, - MethodCallId methodCallId) + NSMutableArray *retainedObjectsForInvocation) { - const bool isSync = returnType != VoidKind && returnType != PromiseKind; const char *moduleName = name_.c_str(); const id module = instance_; - if (isSync) { - [performanceLogger_ syncMethodCallArgumentConversionStart:moduleName - methodName:methodName - methodCallId:methodCallId]; + if (isMethodSync(returnType)) { + TurboModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName); } else { - [performanceLogger_ asyncMethodCallArgumentConversionStart:moduleName - methodName:methodName - methodCallId:methodCallId]; + TurboModulePerfLogger::asyncMethodCallArgConversionStart(moduleName, methodName); } NSInvocation *inv = @@ -594,32 +587,28 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( } } - if (isSync) { - [performanceLogger_ syncMethodCallArgumentConversionEnd:moduleName methodName:methodName methodCallId:methodCallId]; + if (isMethodSync(returnType)) { + TurboModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName); } else { - [performanceLogger_ asyncMethodCallArgumentConversionEnd:moduleName - methodName:methodName - methodCallId:methodCallId]; + TurboModulePerfLogger::asyncMethodCallArgConversionEnd(moduleName, methodName); } return inv; } +bool ObjCTurboModule::isMethodSync(TurboModuleMethodValueKind returnType) +{ + return isSyncModule_ || !(returnType == VoidKind || returnType == PromiseKind); +} + ObjCTurboModule::ObjCTurboModule(const InitParams ¶ms) : TurboModule(params.moduleName, params.jsInvoker), instance_(params.instance), nativeInvoker_(params.nativeInvoker), - performanceLogger_(params.perfLogger) + isSyncModule_(params.isSyncModule) { } -MethodCallId ObjCTurboModule::methodCallId_{0}; - -MethodCallId ObjCTurboModule::getNewMethodCallId() -{ - return methodCallId_++; -} - jsi::Value ObjCTurboModule::invokeObjCMethod( jsi::Runtime &runtime, TurboModuleMethodValueKind returnType, @@ -628,20 +617,18 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( const jsi::Value *args, size_t count) { - MethodCallId methodCallId = getNewMethodCallId(); - const bool isSync = returnType != VoidKind && returnType != PromiseKind; const char *moduleName = name_.c_str(); const char *methodName = methodNameStr.c_str(); - if (isSync) { - [performanceLogger_ syncMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId]; + if (isMethodSync(returnType)) { + TurboModulePerfLogger::syncMethodCallStart(moduleName, methodName); } else { - [performanceLogger_ asyncMethodCallStart:moduleName methodName:methodName methodCallId:methodCallId]; + TurboModulePerfLogger::asyncMethodCallStart(moduleName, methodName); } NSMutableArray *retainedObjectsForInvocation = [NSMutableArray arrayWithCapacity:count + 2]; - NSInvocation *inv = getMethodInvocation( - runtime, returnType, methodName, selector, args, count, retainedObjectsForInvocation, methodCallId); + NSInvocation *inv = + getMethodInvocation(runtime, returnType, methodName, selector, args, count, retainedObjectsForInvocation); jsi::Value returnValue = returnType == PromiseKind ? createPromise( @@ -653,14 +640,14 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback( [retainedObjectsForInvocation addObject:resolveBlock]; [retainedObjectsForInvocation addObject:rejectBlock]; // The return type becomes void in the ObjC side. - performMethodInvocation(runtime, VoidKind, methodName, inv, retainedObjectsForInvocation, methodCallId); + performMethodInvocation(runtime, VoidKind, methodName, inv, retainedObjectsForInvocation); }) - : performMethodInvocation(runtime, returnType, methodName, inv, retainedObjectsForInvocation, methodCallId); + : performMethodInvocation(runtime, returnType, methodName, inv, retainedObjectsForInvocation); - if (isSync) { - [performanceLogger_ syncMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId]; + if (isMethodSync(returnType)) { + TurboModulePerfLogger::syncMethodCallEnd(moduleName, methodName); } else { - [performanceLogger_ asyncMethodCallEnd:moduleName methodName:methodName methodCallId:methodCallId]; + TurboModulePerfLogger::asyncMethodCallEnd(moduleName, methodName); } return returnValue; diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h index 870d1a7d8a2ec8..87c0c96f07e10f 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h @@ -5,6 +5,10 @@ * LICENSE file in the root directory of this source tree. */ +#pragma once + +#import + #import "RCTTurboModule.h" @protocol RCTTurboModuleManagerDelegate @@ -41,11 +45,6 @@ delegate:(id)delegate jsInvoker:(std::shared_ptr)jsInvoker; -- (instancetype)initWithBridge:(RCTBridge *)bridge - delegate:(id)delegate - jsInvoker:(std::shared_ptr)jsInvoker - performanceLogger:(id)performanceLogger; - - (void)installJSBindingWithRuntime:(facebook::jsi::Runtime *)runtime; - (void)invalidate; diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm index f6179e0d344995..accd2a2fd4e622 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm @@ -23,8 +23,9 @@ #import #import #import +#import -using namespace facebook; +using namespace facebook::react; /** * A global variable whose address we use to associate method queues to id objects. @@ -32,8 +33,15 @@ static char kAssociatedMethodQueueKey; namespace { +int32_t getUniqueId() +{ + static std::atomic counter{0}; + return counter++; +} + class TurboModuleHolder { private: + const int32_t moduleId_; id module_; bool isTryingToCreateModule_; bool isDoneCreatingModule_; @@ -41,6 +49,16 @@ std::condition_variable cv_; public: + TurboModuleHolder() + : moduleId_(getUniqueId()), module_(nil), isTryingToCreateModule_(false), isDoneCreatingModule_(false) + { + } + + int32_t getModuleId() const + { + return moduleId_; + } + void setModule(id module) { module_ = module; @@ -83,7 +101,7 @@ bool isCreatingModule() const } }; -class MethodQueueNativeCallInvoker : public facebook::react::CallInvoker { +class MethodQueueNativeCallInvoker : public CallInvoker { private: dispatch_queue_t methodQueue_; @@ -130,8 +148,7 @@ static Class getFallbackClassFromName(const char *name) @implementation RCTTurboModuleManager { jsi::Runtime *_runtime; - std::shared_ptr _jsInvoker; - id _performanceLogger; + std::shared_ptr _jsInvoker; __weak id _delegate; __weak RCTBridge *_bridge; @@ -142,11 +159,10 @@ @implementation RCTTurboModuleManager { * they want to be long-lived or short-lived. * * All instances of TurboModuleHolder are owned by the _turboModuleHolders map. - * We create TurboModuleHolder via operator[] inside getOrCreateTurboModuleHolder(). - * Henceforth, we only refer to TurboModuleHolders via pointers to entries in the _turboModuleHolders map. + * We only reference TurboModuleHolders via pointers to entries in the _turboModuleHolders map. */ std::unordered_map _turboModuleHolders; - std::unordered_map> _turboModuleCache; + std::unordered_map> _turboModuleCache; // Enforce synchronous access into _delegate std::mutex _turboModuleManagerDelegateMutex; @@ -158,22 +174,13 @@ @implementation RCTTurboModuleManager { - (instancetype)initWithBridge:(RCTBridge *)bridge delegate:(id)delegate - jsInvoker:(std::shared_ptr)jsInvoker -{ - return [self initWithBridge:bridge delegate:delegate jsInvoker:jsInvoker performanceLogger:nil]; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge - delegate:(id)delegate - jsInvoker:(std::shared_ptr)jsInvoker - performanceLogger:(id)performanceLogger + jsInvoker:(std::shared_ptr)jsInvoker { if (self = [super init]) { _jsInvoker = jsInvoker; _delegate = delegate; _bridge = bridge; _invalidating = false; - _performanceLogger = performanceLogger; // Necessary to allow NativeModules to lookup TurboModules [bridge setRCTTurboModuleLookupDelegate:self]; @@ -213,34 +220,40 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name * (for now). */ -- (std::shared_ptr)provideTurboModule:(const char *)moduleName +- (std::shared_ptr)provideTurboModule:(const char *)moduleName { auto turboModuleLookup = _turboModuleCache.find(moduleName); if (turboModuleLookup != _turboModuleCache.end()) { - [_performanceLogger createTurboModuleCacheHit:moduleName]; + TurboModulePerfLogger::moduleJSRequireBeginningCacheHit(moduleName); + TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName); return turboModuleLookup->second; } + TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName); + /** * Step 1: Look for pure C++ modules. * Pure C++ modules get priority. */ if ([_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) { - [_performanceLogger getCppTurboModuleFromTMMDelegateStart:moduleName]; + int32_t moduleId = getUniqueId(); + TurboModulePerfLogger::moduleCreateStart(moduleName, moduleId); auto turboModule = [_delegate getTurboModule:moduleName jsInvoker:_jsInvoker]; - [_performanceLogger getCppTurboModuleFromTMMDelegateEnd:moduleName]; if (turboModule != nullptr) { _turboModuleCache.insert({moduleName, turboModule}); + TurboModulePerfLogger::moduleCreateEnd(moduleName, moduleId); return turboModule; } + + TurboModulePerfLogger::moduleCreateFail(moduleName, moduleId); } /** * Step 2: Look for platform-specific modules. */ - [_performanceLogger createRCTTurboModuleStart:moduleName]; id module = [self provideRCTTurboModule:moduleName]; - [_performanceLogger createRCTTurboModuleEnd:moduleName]; + + TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName); // If we request that a TurboModule be created, its respective ObjC class must exist // If the class doesn't exist, then provideRCTTurboModule returns nil @@ -255,8 +268,7 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name /** * Step 2c: Create and native CallInvoker from the TurboModule's method queue. */ - std::shared_ptr nativeInvoker = - std::make_shared(methodQueue); + std::shared_ptr nativeInvoker = std::make_shared(methodQueue); /** * Have RCTCxxBridge decorate native CallInvoker, so that it's aware of TurboModule async method calls. @@ -266,20 +278,18 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name nativeInvoker = [_bridge decorateNativeCallInvoker:nativeInvoker]; } - facebook::react::ObjCTurboModule::InitParams params = { + ObjCTurboModule::InitParams params = { .moduleName = moduleName, .instance = module, .jsInvoker = _jsInvoker, .nativeInvoker = nativeInvoker, - .perfLogger = _performanceLogger, + .isSyncModule = methodQueue == RCTJSThread, }; // If RCTTurboModule supports creating its own C++ TurboModule object, // allow it to do so. if ([module respondsToSelector:@selector(getTurboModule:)]) { - [_performanceLogger getTurboModuleFromRCTTurboModuleStart:moduleName]; auto turboModule = [module getTurboModule:params]; - [_performanceLogger getTurboModuleFromRCTTurboModuleEnd:moduleName]; assert(turboModule != nullptr); _turboModuleCache.insert({moduleName, turboModule}); return turboModule; @@ -292,9 +302,7 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name if ([moduleClass isSubclassOfClass:RCTCxxModule.class]) { // Use TurboCxxModule compat class to wrap the CxxModule instance. // This is only for migration convenience, despite less performant. - [_performanceLogger getTurboModuleFromRCTCxxModuleStart:moduleName]; - auto turboModule = std::make_shared([((RCTCxxModule *)module) createModule], _jsInvoker); - [_performanceLogger getTurboModuleFromRCTCxxModuleEnd:moduleName]; + auto turboModule = std::make_shared([((RCTCxxModule *)module) createModule], _jsInvoker); _turboModuleCache.insert({moduleName, turboModule}); return turboModule; } @@ -302,9 +310,7 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name /** * Step 2e: Return an exact sub-class of ObjC TurboModule */ - [_performanceLogger getTurboModuleFromTMMDelegateStart:moduleName]; auto turboModule = [_delegate getTurboModule:moduleName initParams:params]; - [_performanceLogger getTurboModuleFromTMMDelegateEnd:moduleName]; if (turboModule != nullptr) { _turboModuleCache.insert({moduleName, turboModule}); } @@ -332,10 +338,21 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name moduleHolder = &_turboModuleHolders[moduleName]; } - return [self _provideRCTTurboModule:moduleName moduleHolder:moduleHolder]; + TurboModulePerfLogger::moduleCreateStart(moduleName, moduleHolder->getModuleId()); + id module = [self _provideRCTTurboModule:moduleName moduleHolder:moduleHolder shouldPerfLog:YES]; + + if (module) { + TurboModulePerfLogger::moduleCreateEnd(moduleName, moduleHolder->getModuleId()); + } else { + TurboModulePerfLogger::moduleCreateFail(moduleName, moduleHolder->getModuleId()); + } + + return module; } -- (id)_provideRCTTurboModule:(const char *)moduleName moduleHolder:(TurboModuleHolder *)moduleHolder +- (id)_provideRCTTurboModule:(const char *)moduleName + moduleHolder:(TurboModuleHolder *)moduleHolder + shouldPerfLog:(BOOL)shouldPerfLog { bool shouldCreateModule = false; @@ -343,6 +360,9 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name std::lock_guard guard(moduleHolder->mutex()); if (moduleHolder->isDoneCreatingModule()) { + if (shouldPerfLog) { + TurboModulePerfLogger::moduleCreateCacheHit(moduleName, moduleHolder->getModuleId()); + } return moduleHolder->getModule(); } @@ -358,7 +378,6 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name /** * Step 2a: Resolve platform-specific class. */ - [_performanceLogger getRCTTurboModuleClassStart:moduleName]; if ([_delegate respondsToSelector:@selector(getModuleClassFromName:)]) { std::lock_guard delegateGuard(_turboModuleManagerDelegateMutex); @@ -370,13 +389,13 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name moduleClass = getFallbackClassFromName(moduleName); } - [_performanceLogger getRCTTurboModuleClassEnd:moduleName]; - __block id module = nil; if ([moduleClass conformsToProtocol:@protocol(RCTTurboModule)]) { dispatch_block_t work = ^{ - module = [self _createAndSetUpRCTTurboModule:moduleClass moduleName:moduleName]; + module = [self _createAndSetUpRCTTurboModule:moduleClass + moduleName:moduleName + moduleId:moduleHolder->getModuleId()]; }; if ([self _requiresMainQueueSetup:moduleClass]) { @@ -422,15 +441,17 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name * - The main thread (if the TurboModule requires main queue init), blocking the thread that calls * provideRCTTurboModule:. */ -- (id)_createAndSetUpRCTTurboModule:(Class)moduleClass moduleName:(const char *)moduleName +- (id)_createAndSetUpRCTTurboModule:(Class)moduleClass + moduleName:(const char *)moduleName + moduleId:(int32_t)moduleId { id module = nil; /** * Step 2b: Ask hosting application/delegate to instantiate this class */ - [_performanceLogger getRCTTurboModuleInstanceStart:moduleName]; + TurboModulePerfLogger::moduleCreateConstructStart(moduleName, moduleId); if ([_delegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) { std::lock_guard delegateGuard(_turboModuleManagerDelegateMutex); @@ -438,10 +459,9 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name } else { module = [moduleClass new]; } + TurboModulePerfLogger::moduleCreateConstructEnd(moduleName, moduleId); - [_performanceLogger getRCTTurboModuleInstanceEnd:moduleName]; - - [_performanceLogger setupRCTTurboModuleStart:moduleName]; + TurboModulePerfLogger::moduleCreateSetUpStart(moduleName, moduleId); if ([module respondsToSelector:@selector(setTurboModuleLookupDelegate:)]) { [module setTurboModuleLookupDelegate:self]; @@ -457,8 +477,6 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name * NativeModule. */ if ([module respondsToSelector:@selector(bridge)] && _bridge) { - [_performanceLogger attachRCTBridgeToRCTTurboModuleStart:moduleName]; - /** * Just because a NativeModule has the `bridge` method, it doesn't mean * that it has synthesized the bridge in its implementation. Therefore, @@ -483,8 +501,6 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name "or provide your own setter method.", RCTBridgeModuleNameForClass([module class])); } - - [_performanceLogger attachRCTBridgeToRCTTurboModuleEnd:moduleName]; } /** @@ -493,8 +509,6 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name * `@synthesize methodQueue = _methodQueue` */ - [_performanceLogger attachMethodQueueToRCTTurboModuleStart:moduleName]; - dispatch_queue_t methodQueue = nil; BOOL moduleHasMethodQueueGetter = [module respondsToSelector:@selector(methodQueue)]; @@ -541,8 +555,6 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name */ objc_setAssociatedObject(module, &kAssociatedMethodQueueKey, methodQueue, OBJC_ASSOCIATION_RETAIN); - [_performanceLogger attachMethodQueueToRCTTurboModuleEnd:moduleName]; - /** * NativeModules that implement the RCTFrameUpdateObserver protocol * require registration with RCTDisplayLink. @@ -551,10 +563,8 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name * rollout. */ if (_bridge) { - [_performanceLogger registerRCTTurboModuleForFrameUpdatesStart:moduleName]; RCTModuleData *data = [[RCTModuleData alloc] initWithModuleInstance:(id)module bridge:_bridge]; [_bridge registerModuleForFrameUpdates:(id)module withModuleData:data]; - [_performanceLogger registerRCTTurboModuleForFrameUpdatesEnd:moduleName]; } /** @@ -563,14 +573,12 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name * TODO(T41180176): Investigate whether we can delete this after TM * rollout. */ - [_performanceLogger dispatchDidInitializeModuleNotificationForRCTTurboModuleStart:moduleName]; [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification object:_bridge userInfo:@{@"module" : module, @"bridge" : RCTNullIfNil([_bridge parentBridge])}]; - [_performanceLogger dispatchDidInitializeModuleNotificationForRCTTurboModuleEnd:moduleName]; - [_performanceLogger setupRCTTurboModuleEnd:moduleName]; + TurboModulePerfLogger::moduleCreateSetUpEnd(moduleName, moduleId); return module; } @@ -643,40 +651,42 @@ - (void)installJSBindingWithRuntime:(jsi::Runtime *)runtime __weak __typeof(self) weakSelf = self; - react::TurboModuleBinding::install( - *_runtime, - [weakSelf, - performanceLogger = _performanceLogger](const std::string &name) -> std::shared_ptr { - if (!weakSelf) { - return nullptr; - } + TurboModuleBinding::install(*_runtime, [weakSelf](const std::string &name) -> std::shared_ptr { + if (!weakSelf) { + return nullptr; + } - __strong __typeof(self) strongSelf = weakSelf; + auto moduleName = name.c_str(); - auto moduleName = name.c_str(); - auto moduleWasNotInitialized = ![strongSelf moduleIsInitialized:moduleName]; - if (moduleWasNotInitialized) { - [strongSelf->_bridge.performanceLogger markStartForTag:RCTPLTurboModuleSetup]; - } + TurboModulePerfLogger::moduleJSRequireBeginningStart(moduleName); - [performanceLogger createTurboModuleStart:moduleName]; + __strong __typeof(self) strongSelf = weakSelf; - /** - * By default, all TurboModules are long-lived. - * Additionally, if a TurboModule with the name `name` isn't found, then we - * trigger an assertion failure. - */ - auto turboModule = [strongSelf provideTurboModule:moduleName]; + auto moduleWasNotInitialized = ![strongSelf moduleIsInitialized:moduleName]; + if (moduleWasNotInitialized) { + [strongSelf->_bridge.performanceLogger markStartForTag:RCTPLTurboModuleSetup]; + } - [performanceLogger createTurboModuleEnd:moduleName]; + /** + * By default, all TurboModules are long-lived. + * Additionally, if a TurboModule with the name `name` isn't found, then we + * trigger an assertion failure. + */ + auto turboModule = [strongSelf provideTurboModule:moduleName]; - if (moduleWasNotInitialized && [strongSelf moduleIsInitialized:moduleName]) { - [strongSelf->_bridge.performanceLogger markStopForTag:RCTPLTurboModuleSetup]; - [strongSelf notifyAboutTurboModuleSetup:moduleName]; - } + if (moduleWasNotInitialized && [strongSelf moduleIsInitialized:moduleName]) { + [strongSelf->_bridge.performanceLogger markStopForTag:RCTPLTurboModuleSetup]; + [strongSelf notifyAboutTurboModuleSetup:moduleName]; + } + + if (turboModule) { + TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName); + } else { + TurboModulePerfLogger::moduleJSRequireEndingFail(moduleName); + } - return turboModule; - }); + return turboModule; + }); } #pragma mark RCTTurboModuleLookupDelegate @@ -737,7 +747,9 @@ - (void)bridgeDidInvalidateModules:(NSNotification *)notification * for TurboModule init to finish before calling invalidate on it. So, we call _provideRCTTurboModule:moduleHolder, * because it's guaranteed to return a fully initialized NativeModule. */ - id module = [self _provideRCTTurboModule:moduleName.c_str() moduleHolder:moduleHolder]; + id module = [self _provideRCTTurboModule:moduleName.c_str() + moduleHolder:moduleHolder + shouldPerfLog:NO]; if ([module respondsToSelector:@selector(invalidate)]) { if ([module respondsToSelector:@selector(methodQueue)]) { diff --git a/ReactCommon/turbomodule/samples/BUCK b/ReactCommon/turbomodule/samples/BUCK index 44173730827899..ae15f4706b606f 100644 --- a/ReactCommon/turbomodule/samples/BUCK +++ b/ReactCommon/turbomodule/samples/BUCK @@ -26,7 +26,6 @@ rn_xplat_cxx_library( ], prefix = "ReactCommon", ), - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbandroid_srcs = glob( [ "platform/android/**/*.cpp", @@ -37,7 +36,6 @@ rn_xplat_cxx_library( "-fobjc-arc-exceptions", ], fbobjc_inherited_buck_flags = get_static_library_ios_flags(), - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = OBJC_ARC_PREPROCESSOR_FLAGS + get_preprocessor_flags_for_build_mode(), force_static = True, ios_deps = [ @@ -63,6 +61,7 @@ rn_xplat_cxx_library( "platform/ios/**/*.mm", ], ), + labels = ["supermodule:xplat/default/public.react_native.infra"], platforms = (ANDROID, APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/utils/BUCK b/ReactCommon/utils/BUCK index c9660e082d7157..9b525a1f1e037a 100644 --- a/ReactCommon/utils/BUCK +++ b/ReactCommon/utils/BUCK @@ -39,12 +39,11 @@ rn_xplat_cxx_library( "-std=c++14", "-Wall", ], - fbandroid_labels = ["supermodule:android/default/public.react_native.infra"], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, fbobjc_frameworks = ["Foundation"], - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), force_static = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], macosx_tests_override = [], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ diff --git a/ReactCommon/yoga/yoga/Utils.cpp b/ReactCommon/yoga/yoga/Utils.cpp index 761f3515ed6cae..f6e55d0d8fcc5c 100644 --- a/ReactCommon/yoga/yoga/Utils.cpp +++ b/ReactCommon/yoga/yoga/Utils.cpp @@ -65,3 +65,7 @@ YGFloatOptional YGFloatOptionalMax(YGFloatOptional op1, YGFloatOptional op2) { } return op1.isUndefined() ? op2 : op1; } + +void throwLogicalErrorWithMessage(const char* message) { + throw std::logic_error(message); +} diff --git a/ReactCommon/yoga/yoga/Utils.h b/ReactCommon/yoga/yoga/Utils.h index bce8dfcab329c9..e9edf2f96268f2 100644 --- a/ReactCommon/yoga/yoga/Utils.h +++ b/ReactCommon/yoga/yoga/Utils.h @@ -141,3 +141,5 @@ inline YGFloatOptional YGResolveValueMargin( const float ownerSize) { return value.isAuto() ? YGFloatOptional{0} : YGResolveValue(value, ownerSize); } + +void throwLogicalErrorWithMessage(const char* message); diff --git a/ReactCommon/yoga/yoga/Yoga.cpp b/ReactCommon/yoga/yoga/Yoga.cpp index c2a5c286f99580..91e09c15dac2bc 100644 --- a/ReactCommon/yoga/yoga/Yoga.cpp +++ b/ReactCommon/yoga/yoga/Yoga.cpp @@ -249,9 +249,6 @@ YOGA_EXPORT YGNodeRef YGNodeClone(YGNodeRef oldNode) { static YGConfigRef YGConfigClone(const YGConfig& oldConfig) { const YGConfigRef config = new YGConfig(oldConfig); YGAssert(config != nullptr, "Could not allocate memory for config"); - if (config == nullptr) { - abort(); - } gConfigInstanceCount++; return config; } @@ -4341,6 +4338,7 @@ YOGA_EXPORT void YGConfigSetShouldDiffLayoutWithoutLegacyStretchBehaviour( void YGAssert(const bool condition, const char* message) { if (!condition) { Log::log(YGNodeRef{nullptr}, YGLogLevelFatal, nullptr, "%s\n", message); + throwLogicalErrorWithMessage(message); } } @@ -4350,6 +4348,7 @@ void YGAssertWithNode( const char* message) { if (!condition) { Log::log(node, YGLogLevelFatal, nullptr, "%s\n", message); + throwLogicalErrorWithMessage(message); } } @@ -4359,6 +4358,7 @@ void YGAssertWithConfig( const char* message) { if (!condition) { Log::log(config, YGLogLevelFatal, nullptr, "%s\n", message); + throwLogicalErrorWithMessage(message); } } diff --git a/ReactCommon/yoga/yoga/log.cpp b/ReactCommon/yoga/yoga/log.cpp index fe6fbbc6dc944a..eb3da039c3d071 100644 --- a/ReactCommon/yoga/yoga/log.cpp +++ b/ReactCommon/yoga/yoga/log.cpp @@ -26,10 +26,6 @@ void vlog( va_list args) { YGConfig* logConfig = config != nullptr ? config : YGConfigGetDefault(); logConfig->log(logConfig, node, level, context, format, args); - - if (level == YGLogLevelFatal) { - abort(); - } } } // namespace diff --git a/android-patches/patches/Focus/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/android-patches/patches/Focus/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 1fbe78695d223b..3900a74d85bf05 100644 --- a/android-patches/patches/Focus/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/android-patches/patches/Focus/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -1,6 +1,16 @@ ---- "E:\\github\\rnm-63-fresh\\ReactAndroid\\src\\main\\java\\com\\facebook\\react\\views\\view\\ReactViewManager.java" 2020-10-27 20:26:16.993188900 -0700 -+++ "E:\\github\\rnm-63\\ReactAndroid\\src\\main\\java\\com\\facebook\\react\\views\\view\\ReactViewManager.java" 2020-10-13 21:46:27.236639400 -0700 -@@ -48,8 +48,13 @@ +diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +index 851ec10c6..4fe93de49 100644 +--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java ++++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +@@ -25,6 +25,7 @@ import com.facebook.react.uimanager.PointerEvents; + import com.facebook.react.uimanager.Spacing; + import com.facebook.react.uimanager.ThemedReactContext; + import com.facebook.react.uimanager.UIManagerHelper; ++import com.facebook.react.uimanager.UIManagerModule; + import com.facebook.react.uimanager.ViewProps; + import com.facebook.react.uimanager.annotations.ReactProp; + import com.facebook.react.uimanager.annotations.ReactPropGroup; +@@ -48,8 +49,13 @@ public class ReactViewManager extends ReactClippingViewManager { Spacing.START, Spacing.END, }; @@ -16,7 +26,7 @@ private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate"; @ReactProp(name = "accessible") -@@ -120,6 +125,36 @@ +@@ -120,6 +126,36 @@ public class ReactViewManager extends ReactClippingViewManager { } } @@ -53,7 +63,7 @@ @ReactProp(name = "borderStyle") public void setBorderStyle(ReactViewGroup view, @Nullable String borderStyle) { view.setBorderStyle(borderStyle); -@@ -289,7 +324,7 @@ +@@ -287,7 +323,7 @@ public class ReactViewManager extends ReactClippingViewManager { @Override public Map getCommandsMap() { @@ -62,7 +72,7 @@ public Map getCommandsMap() { } @Override -@@ -305,6 +340,16 @@ +@@ -303,6 +339,16 @@ public class ReactViewManager extends ReactClippingViewManager { handleSetPressed(root, args); break; } @@ -79,7 +89,7 @@ public Map getCommandsMap() { } } -@@ -321,6 +366,16 @@ +@@ -319,6 +365,16 @@ public class ReactViewManager extends ReactClippingViewManager { handleSetPressed(root, args); break; } diff --git a/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 4bcd4b7693eb9a..82112a2052a785 100644 --- a/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -1,6 +1,8 @@ ---- "E:\\github\\rnm-63-fresh\\ReactAndroid\\src\\main\\java\\com\\facebook\\react\\ReactInstanceManager.java" 2020-10-27 20:26:16.728167300 -0700 -+++ "E:\\github\\rnm-63\\ReactAndroid\\src\\main\\java\\com\\facebook\\react\\ReactInstanceManager.java" 2020-10-13 21:26:17.779198100 -0700 -@@ -51,6 +51,7 @@ +diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +index 63ac2ec11..26fee8860 100644 +--- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java ++++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +@@ -51,6 +51,7 @@ import com.facebook.infer.annotation.ThreadConfined; import com.facebook.infer.annotation.ThreadSafe; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.CatalystInstance; @@ -8,7 +10,7 @@ import com.facebook.react.bridge.CatalystInstanceImpl; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JSIModule; -@@ -173,6 +174,7 @@ +@@ -173,6 +174,7 @@ public class ReactInstanceManager { private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final @Nullable JSIModulePackage mJSIModulePackage; private List mViewManagers; @@ -16,7 +18,7 @@ private class ReactContextInitParams { private final JavaScriptExecutorFactory mJsExecutorFactory; -@@ -922,6 +924,15 @@ +@@ -193,6 +195,15 @@ public class ReactInstanceManager { } } @@ -29,16 +31,16 @@ private class ReactContextInitParams { + mCatalystInstanceEventListener = catalystInstanceEventListener; + } + - /** Add a listener to be notified of react instance events. */ - public void addReactInstanceEventListener(ReactInstanceEventListener listener) { - mReactInstanceEventListeners.add(listener); -@@ -1245,7 +1256,8 @@ + /** Creates a builder that is capable of creating an instance of {@link ReactInstanceManager}. */ + public static ReactInstanceManagerBuilder builder() { + return new ReactInstanceManagerBuilder(); +@@ -1266,7 +1277,8 @@ public class ReactInstanceManager { .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSBundleLoader(jsBundleLoader) - .setNativeModuleCallExceptionHandler(exceptionHandler); + .setNativeModuleCallExceptionHandler(exceptionHandler) -+ .setCatalystInstanceEventListener(mCatalystInstanceEventListener); ++ .setCatalystInstanceEventListener(mCatalystInstanceEventListener); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START); // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp diff --git a/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk b/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk index 89ac240b7673eb..1141aa30bb0fe6 100644 --- a/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk +++ b/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk @@ -1,16 +1,17 @@ ---- "E:\\github\\rnm-63-fresh\\ReactAndroid\\src\\main\\jni\\react\\jni\\Android.mk" 2020-10-27 20:26:17.023172300 -0700 -+++ "E:\\github\\rnm-63\\ReactAndroid\\src\\main\\jni\\react\\jni\\Android.mk" 2020-10-13 21:47:10.404176500 -0700 -@@ -71,6 +71,7 @@ - $(call import-module,callinvoker) +diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk +index 38a51019e..7425e65a5 100644 +--- a/ReactAndroid/src/main/jni/react/jni/Android.mk ++++ b/ReactAndroid/src/main/jni/react/jni/Android.mk +@@ -72,6 +72,7 @@ $(call import-module,callinvoker) + $(call import-module,reactperflogger) $(call import-module,hermes) $(call import-module,runtimeexecutor) +$(call import-module,v8jsi) include $(REACT_SRC_DIR)/turbomodule/core/jni/Android.mk -@@ -82,3 +83,4 @@ +@@ -83,3 +84,4 @@ include $(REACT_SRC_DIR)/jscexecutor/Android.mk include $(REACT_SRC_DIR)/../hermes/reactexecutor/Android.mk include $(REACT_SRC_DIR)/../hermes/instrumentation/Android.mk include $(REACT_SRC_DIR)/modules/blob/jni/Android.mk +include $(REACT_SRC_DIR)/v8executor/Android.mk -\ No newline at end of file diff --git a/jest/renderer.js b/jest/renderer.js index e9f0cde6c4ad10..144b53c26292b7 100644 --- a/jest/renderer.js +++ b/jest/renderer.js @@ -15,8 +15,8 @@ const React = require('react'); const ShallowRenderer = require('react-test-renderer/shallow'); const TestRenderer = require('react-test-renderer'); -/* $FlowFixMe(>=0.122.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.122.0 was deployed. To see the error, delete this comment +/* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an error + * found when Flow v0.125.1 was deployed. To see the error, delete this comment * and run Flow. */ const renderer = new ShallowRenderer(); diff --git a/package.json b/package.json index 96b394ccb04919..e6042c7e61cc92 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "pretty-format": "^26.0.1", "promise": "^8.0.3", "prop-types": "^15.7.2", + "qs": "^6.5.1", "react-devtools-core": "^4.6.0", "react-refresh": "^0.4.0", "regenerator-runtime": "^0.13.2", @@ -143,7 +144,7 @@ "eslint-plugin-react-hooks": "^3.0.0", "eslint-plugin-react-native": "3.8.1", "eslint-plugin-relay": "1.7.1", - "flow-bin": "^0.124.0", + "flow-bin": "^0.125.1", "flow-remove-types": "1.2.3", "hermes-engine-darwin": "~0.5.0", "jest": "^26.0.1", diff --git a/packages/eslint-config-react-native-community/README.md b/packages/eslint-config-react-native-community/README.md index d0dd6a118d3cf3..367ab70030ded3 100644 --- a/packages/eslint-config-react-native-community/README.md +++ b/packages/eslint-config-react-native-community/README.md @@ -5,7 +5,7 @@ ## Installation ``` -yarn add --dev eslint @react-native-community/eslint-config +yarn add --dev eslint prettier @react-native-community/eslint-config ``` *Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* diff --git a/packages/react-native-codegen/DEFS.bzl b/packages/react-native-codegen/DEFS.bzl index f54615a0494941..ce5d7225cd5bec 100644 --- a/packages/react-native-codegen/DEFS.bzl +++ b/packages/react-native-codegen/DEFS.bzl @@ -219,7 +219,6 @@ def rn_codegen_components( ], fbobjc_compiler_flags = get_apple_compiler_flags(), fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), - fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js b/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js index 08c99a48791373..dd0258a488ab7d 100644 --- a/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js +++ b/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js @@ -55,7 +55,8 @@ const propSetterTemplate = ` `; const commandsTemplate = ` - public void receiveCommand(::_INTERFACE_CLASSNAME_:: viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { ::_COMMAND_CASES_:: } @@ -198,7 +199,7 @@ function generateCommandCasesString( const commandMethods = component.commands .map(command => { return `case "${command.name}": - viewManager.${toSafeJavaString( + mViewManager.${toSafeJavaString( command.name, false, )}(${getCommandArguments(command)}); diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GeneratePropsJavaDelegate-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GeneratePropsJavaDelegate-test.js.snap index 914f8e56da71a9..fda2e0e535ed63 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GeneratePropsJavaDelegate-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GeneratePropsJavaDelegate-test.js.snap @@ -214,13 +214,14 @@ public class CommandNativeComponentManagerDelegate viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case \\"flashScrollIndicators\\": - viewManager.flashScrollIndicators(view); + mViewManager.flashScrollIndicators(view); break; case \\"allTypes\\": - viewManager.allTypes(view, args.getInt(0), (float) args.getDouble(1), args.getDouble(2), args.getString(3), args.getBoolean(4)); + mViewManager.allTypes(view, args.getInt(0), (float) args.getDouble(1), args.getDouble(2), args.getString(3), args.getBoolean(4)); break; } } @@ -264,13 +265,14 @@ public class CommandNativeComponentManagerDelegate viewManager, T view, String commandName, ReadableArray args) { + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { case \\"handleRootTag\\": - viewManager.handleRootTag(view, args.getDouble(0)); + mViewManager.handleRootTag(view, args.getDouble(0)); break; case \\"hotspotUpdate\\": - viewManager.hotspotUpdate(view, args.getInt(0), args.getInt(1)); + mViewManager.hotspotUpdate(view, args.getInt(0), args.getInt(1)); break; } } diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js index 416e7c549fb03d..f6ff56e544cdca 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js @@ -51,12 +51,62 @@ function getSafePropertyName(name: string) { return name; } +function getNamespacedStructName(structName: string, propertyName: string) { + return `JS::Native::_MODULE_NAME_::::Spec${structName}${capitalizeFirstLetter( + getSafePropertyName(propertyName), + )}`; +} + +function getElementTypeForArray( + property: ObjectParamTypeAnnotation, + name: string, +): string { + const {typeAnnotation} = property; + if (typeAnnotation.type !== 'ArrayTypeAnnotation') { + throw new Error( + `Cannot get array element type for non-array type ${typeAnnotation.type}`, + ); + } + + if (!typeAnnotation.elementType) { + return 'id'; + } + + const {type} = typeAnnotation.elementType; + switch (type) { + case 'StringTypeAnnotation': + return 'NSString *'; + case 'DoubleTypeAnnotation': + case 'NumberTypeAnnotation': + case 'FloatTypeAnnotation': + case 'Int32TypeAnnotation': + return 'double'; + case 'ObjectTypeAnnotation': + return getNamespacedStructName(name, property.name); + case 'GenericObjectTypeAnnotation': + // TODO T67565166: Generic objects are not type safe and should be disallowed in the schema. + return 'id'; + case 'BooleanTypeAnnotation': + case 'AnyObjectTypeAnnotation': + case 'AnyTypeAnnotation': + case 'ArrayTypeAnnotation': + case 'FunctionTypeAnnotation': + case 'ReservedFunctionValueTypeAnnotation': + case 'ReservedPropTypeAnnotation': + case 'StringEnumTypeAnnotation': + throw new Error(`Unsupported array element type, found: ${type}"`); + default: + (type: empty); + throw new Error(`Unknown array element type, found: ${type}"`); + } +} + function getInlineMethodSignature( property: ObjectParamTypeAnnotation, name: string, ): string { const {typeAnnotation} = property; - function markOptionalTypeIfNecessary(type) { + function markOptionalTypeIfNecessary(type: string) { if (property.optional) { return `folly::Optional<${type}>`; } @@ -86,22 +136,20 @@ function getInlineMethodSignature( case 'ObjectTypeAnnotation': return ( markOptionalTypeIfNecessary( - `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( - getSafePropertyName(property.name), - )}`, + getNamespacedStructName(name, property.name), ) + ` ${getSafePropertyName(property.name)}() const;` ); case 'GenericObjectTypeAnnotation': case 'AnyTypeAnnotation': - if (property.optional) { - return `id _Nullable ${getSafePropertyName( - property.name, - )}() const;`; - } - return `id ${getSafePropertyName(property.name)}() const;`; + return `id ${ + property.optional ? '_Nullable ' : ' ' + }${getSafePropertyName(property.name)}() const;`; case 'ArrayTypeAnnotation': return `${markOptionalTypeIfNecessary( - 'facebook::react::LazyVector>', + `facebook::react::LazyVector<${getElementTypeForArray( + property, + name, + )}>`, )} ${getSafePropertyName(property.name)}() const;`; case 'FunctionTypeAnnotation': default: @@ -114,18 +162,58 @@ function getInlineMethodImplementation( name: string, ): string { const {typeAnnotation} = property; - function markOptionalTypeIfNecessary(type) { + function markOptionalTypeIfNecessary(type: string): string { if (property.optional) { return `folly::Optional<${type}> `; } return `${type} `; } - function markOptionalValueIfNecessary(value) { + function markOptionalValueIfNecessary(value: string): string { if (property.optional) { return `RCTBridgingToOptional${capitalizeFirstLetter(value)}`; } return `RCTBridgingTo${capitalizeFirstLetter(value)}`; } + function bridgeArrayElementValueIfNecessary(element: string): string { + if (typeAnnotation.type !== 'ArrayTypeAnnotation') { + throw new Error( + `Cannot get array element type for non-array type ${typeAnnotation.type}`, + ); + } + + if (!typeAnnotation.elementType) { + throw new Error(`Cannot get array element type for ${name}`); + } + + const {type} = typeAnnotation.elementType; + switch (type) { + case 'StringTypeAnnotation': + return `RCTBridgingToString(${element})`; + case 'DoubleTypeAnnotation': + case 'NumberTypeAnnotation': + case 'FloatTypeAnnotation': + case 'Int32TypeAnnotation': + return `RCTBridgingToDouble(${element})`; + case 'BooleanTypeAnnotation': + return `RCTBridgingToBool(${element})`; + case 'ObjectTypeAnnotation': + return `${getNamespacedStructName(name, property.name)}(${element})`; + case 'GenericObjectTypeAnnotation': + return element; + case 'AnyObjectTypeAnnotation': + case 'AnyTypeAnnotation': + case 'ArrayTypeAnnotation': + case 'FunctionTypeAnnotation': + case 'ReservedFunctionValueTypeAnnotation': + case 'ReservedPropTypeAnnotation': + case 'StringEnumTypeAnnotation': + case 'TupleTypeAnnotation': + throw new Error(`Unsupported array element type, found: ${type}"`); + default: + (type: empty); + throw new Error(`Unknown array element type, found: ${type}"`); + } + } switch (typeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': @@ -171,34 +259,37 @@ function getInlineMethodImplementation( .replace( /::_RETURN_TYPE_::/, markOptionalTypeIfNecessary( - `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( - getSafePropertyName(property.name), - )}`, + getNamespacedStructName(name, property.name), ), ) .replace( /::_RETURN_VALUE_::/, property.optional - ? `(p == nil ? folly::none : folly::make_optional(JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( - getSafePropertyName(property.name), + ? `(p == nil ? folly::none : folly::make_optional(${getNamespacedStructName( + name, + property.name, )}(p)))` - : `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( - getSafePropertyName(property.name), - )}(p)`, + : `${getNamespacedStructName(name, property.name)}(p)`, ); case 'ArrayTypeAnnotation': return inlineTemplate .replace( /::_RETURN_TYPE_::/, markOptionalTypeIfNecessary( - 'facebook::react::LazyVector>', + `facebook::react::LazyVector<${getElementTypeForArray( + property, + name, + )}>`, ), ) .replace( /::_RETURN_VALUE_::/, - `${markOptionalValueIfNecessary( - 'vec', - )}(p, ^id(id itemValue_0) { return itemValue_0; })`, + `${markOptionalValueIfNecessary('vec')}(p, ^${getElementTypeForArray( + property, + name, + )}(id itemValue_0) { return ${bridgeArrayElementValueIfNecessary( + 'itemValue_0', + )}; })`, ); case 'FunctionTypeAnnotation': default: diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index fd534a9af516fb..110476acc5f7f1 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -609,6 +609,87 @@ const COMPLEX_OBJECTS: SchemaType = { optional: true, }, }, + { + name: 'getArrays', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'options', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'arrayOfNumbers', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'NumberTypeAnnotation', + }, + }, + }, + { + optional: true, + name: 'optionalArrayOfNumbers', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'NumberTypeAnnotation', + }, + }, + }, + { + optional: false, + name: 'arrayOfStrings', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringTypeAnnotation', + }, + }, + }, + { + optional: true, + name: 'optionalArrayOfStrings', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringTypeAnnotation', + }, + }, + }, + { + optional: false, + name: 'arrayOfObjects', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'numberProperty', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + }, + }, + }, + ], + }, + }, + ], + optional: false, + }, + }, ], }, }, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap index e3b257ba994b24..be5e11a7f90a0a 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap @@ -29,11 +29,17 @@ static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_optionalMetho return jsi::Value::undefined(); } +static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getArrays(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getArrays(rt, args[0].getObject(rt)); + return jsi::Value::undefined(); +} + NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule(\\"SampleTurboModule\\", jsInvoker) { methodMap_[\\"difficult\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_difficult}; methodMap_[\\"optionals\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_optionals}; methodMap_[\\"optionalMethod\\"] = MethodMetadata {3, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_optionalMethod}; + methodMap_[\\"getArrays\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getArrays}; } diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 308af3d47cdc32..8a4271cb0206f6 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -25,6 +25,7 @@ public: virtual jsi::Object difficult(jsi::Runtime &rt, const jsi::Object &A) = 0; virtual void optionals(jsi::Runtime &rt, const jsi::Object &A) = 0; virtual void optionalMethod(jsi::Runtime &rt, const jsi::Object &options, const jsi::Function &callback, const jsi::Array &extras) = 0; +virtual void getArrays(jsi::Runtime &rt, const jsi::Object &options) = 0; }; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap index 6005fc72f622f0..83c96819594ef0 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -72,11 +72,32 @@ namespace JS { @end +namespace JS { + namespace NativeSampleTurboModule { + struct SpecGetArraysOptions { + facebook::react::LazyVector arrayOfNumbers() const; + folly::Optional> optionalArrayOfNumbers() const; + facebook::react::LazyVector arrayOfStrings() const; + folly::Optional> optionalArrayOfStrings() const; + facebook::react::LazyVector arrayOfObjects() const; + + SpecGetArraysOptions(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeSampleTurboModule_SpecGetArraysOptions) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecGetArraysOptions:(id)json; +@end + + namespace JS { namespace NativeSampleTurboModule { struct SpecOptionalsA { folly::Optional optionalNumberProperty() const; - folly::Optional>> optionalArrayProperty() const; + folly::Optional> optionalArrayProperty() const; folly::Optional optionalObjectProperty() const; id _Nullable optionalGenericObjectProperty() const; folly::Optional optionalBooleanTypeProperty() const; @@ -179,10 +200,10 @@ inline folly::Optional JS::NativeSampleTurboModule::SpecOptionalsA::opti } -inline folly::Optional>> JS::NativeSampleTurboModule::SpecOptionalsA::optionalArrayProperty() const +inline folly::Optional> JS::NativeSampleTurboModule::SpecOptionalsA::optionalArrayProperty() const { id const p = _v[@\\"optionalArrayProperty\\"]; - return RCTBridgingToOptionalVec(p, ^id(id itemValue_0) { return itemValue_0; }); + return RCTBridgingToOptionalVec(p, ^double(id itemValue_0) { return RCTBridgingToDouble(itemValue_0); }); } @@ -207,6 +228,41 @@ inline folly::Optional JS::NativeSampleTurboModule::SpecOptionalsA::option } +inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfNumbers() const +{ + id const p = _v[@\\"arrayOfNumbers\\"]; + return RCTBridgingToVec(p, ^double(id itemValue_0) { return RCTBridgingToDouble(itemValue_0); }); +} + + +inline folly::Optional> JS::NativeSampleTurboModule::SpecGetArraysOptions::optionalArrayOfNumbers() const +{ + id const p = _v[@\\"optionalArrayOfNumbers\\"]; + return RCTBridgingToOptionalVec(p, ^double(id itemValue_0) { return RCTBridgingToDouble(itemValue_0); }); +} + + +inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfStrings() const +{ + id const p = _v[@\\"arrayOfStrings\\"]; + return RCTBridgingToVec(p, ^NSString *(id itemValue_0) { return RCTBridgingToString(itemValue_0); }); +} + + +inline folly::Optional> JS::NativeSampleTurboModule::SpecGetArraysOptions::optionalArrayOfStrings() const +{ + id const p = _v[@\\"optionalArrayOfStrings\\"]; + return RCTBridgingToOptionalVec(p, ^NSString *(id itemValue_0) { return RCTBridgingToString(itemValue_0); }); +} + + +inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfObjects() const +{ + id const p = _v[@\\"arrayOfObjects\\"]; + return RCTBridgingToVec(p, ^JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjects(id itemValue_0) { return JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjects(itemValue_0); }); +} + + inline bool JS::NativeSampleTurboModule::SpecDifficultAE::D() const { id const p = _v[@\\"D\\"]; @@ -256,6 +312,7 @@ inline double JS::NativeSampleTurboModule::SpecOptionalsAOptionalObjectProperty: - (void) optionalMethod:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback extras:(NSArray * _Nullable)extras; +- (void) getArrays:(JS::NativeSampleTurboModule::SpecGetArraysOptions&)options; @end diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap index 86abf78460cec5..3a278d825a7ae2 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap @@ -40,6 +40,14 @@ Map { @end +@implementation RCTCxxConvert (NativeSampleTurboModule_SpecGetArraysOptions) ++ (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecGetArraysOptions:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + @implementation RCTCxxConvert (NativeSampleTurboModule_SpecDifficultAE) + (RCTManagedPointer *)JS_NativeSampleTurboModule_SpecDifficultAE:(id)json { @@ -73,13 +81,20 @@ namespace facebook { .invokeObjCMethod(rt, VoidKind, \\"optionalMethod\\", @selector(optionalMethod:callback:extras:), args, count); } + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrays(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"getArrays\\", @selector(getArrays:), args, count); + } + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) : ObjCTurboModule(params) { methodMap_[\\"difficult\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_difficult}; methodMap_[\\"optionals\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_optionals}; methodMap_[\\"optionalMethod\\"] = MethodMetadata {3, __hostFunction_NativeSampleTurboModuleSpecJSI_optionalMethod}; + methodMap_[\\"getArrays\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrays}; setMethodArgConversionSelector(@\\"difficult\\", 0, @\\"JS_NativeSampleTurboModule_SpecDifficultA:\\"); setMethodArgConversionSelector(@\\"optionals\\", 0, @\\"JS_NativeSampleTurboModule_SpecOptionalsA:\\"); + setMethodArgConversionSelector(@\\"getArrays\\", 0, @\\"JS_NativeSampleTurboModule_SpecGetArraysOptions:\\"); } } // namespace react } // namespace facebook diff --git a/packages/react-native-codegen/src/parsers/flow/modules/methods.js b/packages/react-native-codegen/src/parsers/flow/modules/methods.js index 7edb91ec1513e4..e6e34b6af2480b 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/methods.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/methods.js @@ -142,6 +142,7 @@ function getElementTypeForArrayOrObject( case 'UnionTypeAnnotation': return undefined; default: + // TODO T67565166: Generic objects are not type safe and should be disallowed in the schema. return { type: 'GenericObjectTypeAnnotation', }; diff --git a/scripts/generate-native-modules-specs-cli.js b/scripts/generate-native-modules-specs-cli.js new file mode 100644 index 00000000000000..2b72610e21b6c4 --- /dev/null +++ b/scripts/generate-native-modules-specs-cli.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +const RNCodegen = require('../packages/react-native-codegen/lib/generators/RNCodegen.js'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const os = require('os'); +const path = require('path'); + +function generateSpec(schemaPath, outputDirectory) { + const libraryName = 'FBReactNativeSpec'; + const moduleSpecName = 'FBReactNativeSpec'; + const schemaText = fs.readFileSync(schemaPath, 'utf-8'); + + if (schemaText == null) { + throw new Error(`Can't find schema at ${schemaPath}`); + } + + const tempOutputDirectory = fs.mkdtempSync( + path.join(os.tmpdir(), 'react-native-codegen-'), + ); + + let schema; + try { + schema = JSON.parse(schemaText); + } catch (err) { + throw new Error(`Can't parse schema to JSON. ${schemaPath}`); + } + + RNCodegen.generate( + {libraryName, schema, outputDirectory: tempOutputDirectory, moduleSpecName}, + { + generators: [ + 'descriptors', + 'events', + 'props', + 'tests', + 'shadow-nodes', + 'modules', + ], + }, + ); + + if (!outputDirectory) { + outputDirectory = path.resolve( + __dirname, + '..', + 'Libraries', + libraryName, + moduleSpecName, + ); + } + mkdirp.sync(outputDirectory); + + const fileNames = [`${moduleSpecName}.h`, `${moduleSpecName}-generated.mm`]; + fileNames.forEach(fileName => { + const newOutput = `${tempOutputDirectory}/${fileName}`; + const prevOutput = `${outputDirectory}/${fileName}`; + fs.copyFileSync(newOutput, prevOutput); + }); +} + +function main() { + const args = process.argv.slice(2); + const schemaPath = args[0]; + const outputDir = args[1]; + generateSpec(schemaPath, outputDir); +} + +main(); diff --git a/scripts/generate-native-modules-specs.sh b/scripts/generate-native-modules-specs.sh index 505a7769a338b0..6f77c36c503603 100755 --- a/scripts/generate-native-modules-specs.sh +++ b/scripts/generate-native-modules-specs.sh @@ -27,26 +27,21 @@ describe () { THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) RN_DIR=$(cd "$THIS_DIR/.." && pwd) -TEMP_DIR=$(mktemp -d /tmp/react-native-codegen-XXXXXXXX) - SRCS_DIR=$(cd "$RN_DIR/Libraries" && pwd) -LIBRARY_NAME="FBReactNativeSpec" -MODULE_SPEC_NAME="FBReactNativeSpec" - SCHEMA_FILE="$RN_DIR/schema-native-modules.json" -OUTPUT_DIR="$SRCS_DIR/$LIBRARY_NAME/$MODULE_SPEC_NAME" - -describe "Generating schema from flow types" -grep --exclude NativeUIManager.js --include=Native\*.js -rnwl "$SRCS_DIR" -e 'export interface Spec extends TurboModule' -e "export default \(TurboModuleRegistry.get(Enforcing)?\('.*\): Spec\);/" \ - | xargs yarn flow-node packages/react-native-codegen/src/cli/combine/combine-js-to-schema-cli.js "$SCHEMA_FILE" - -describe "Generating native code from schema" -yarn flow-node packages/react-native-codegen/buck_tests/generate-tests.js "$SCHEMA_FILE" "$LIBRARY_NAME" "$TEMP_DIR" "$MODULE_SPEC_NAME" if [ -n "$1" ]; then OUTPUT_DIR="$1" - mkdir -p "$OUTPUT_DIR" fi -describe "Copying $MODULE_SPEC_NAME output to $OUTPUT_DIR" -cp "$TEMP_DIR"/$MODULE_SPEC_NAME* "$OUTPUT_DIR/." +pushd "$RN_DIR/packages/react-native-codegen" >/dev/null + yarn + yarn build +popd >/dev/null + +describe "Generating schema from flow types" +grep --exclude NativeUIManager.js --include=Native\*.js -rnwl "$SRCS_DIR" -e 'export interface Spec extends TurboModule' -e "export default \(TurboModuleRegistry.get(Enforcing)?\('.*\): Spec\);/" \ + | xargs yarn node packages/react-native-codegen/lib/cli/combine/combine-js-to-schema-cli.js "$SCHEMA_FILE" + +describe "Generating native code from schema" +yarn node scripts/generate-native-modules-specs-cli.js "$SCHEMA_FILE" "$OUTPUT_DIR" diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index 2cdade9df62ac7..6b1c0f4d084682 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -46,6 +46,7 @@ def use_react_native! (options={}) pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector" pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker" pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor" + pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger" pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon" pod 'Yoga', :path => "#{prefix}/ReactCommon/yoga", :modular_headers => true diff --git a/template.config.js b/template.config.js index 634e9e13c48bcd..e2516f3a4e8278 100644 --- a/template.config.js +++ b/template.config.js @@ -7,5 +7,6 @@ module.exports = { placeholderName: 'HelloWorld', + titlePlaceholder: 'Hello App Display Name', templateDir: './template', }; diff --git a/template/_flowconfig b/template/_flowconfig index 0296b285f8cf61..24b28ccc0e1c21 100644 --- a/template/_flowconfig +++ b/template/_flowconfig @@ -47,10 +47,6 @@ suppress_type=$FlowFixMe suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - [lints] sketchy-null-number=warn sketchy-null-mixed=warn @@ -73,4 +69,4 @@ untyped-import untyped-type-import [version] -^0.124.0 +^0.125.0 diff --git a/template/ios/HelloWorld/Info.plist b/template/ios/HelloWorld/Info.plist index 20f7dd5114c9c5..948d58224f9473 100644 --- a/template/ios/HelloWorld/Info.plist +++ b/template/ios/HelloWorld/Info.plist @@ -26,8 +26,6 @@ NSAppTransportSecurity - NSAllowsArbitraryLoads - NSExceptionDomains localhost diff --git a/tools/build_defs/oss/rn_defs.bzl b/tools/build_defs/oss/rn_defs.bzl index 698ce671363feb..887e25d3651577 100644 --- a/tools/build_defs/oss/rn_defs.bzl +++ b/tools/build_defs/oss/rn_defs.bzl @@ -87,6 +87,9 @@ def react_native_integration_tests_target(path): def react_native_dep(path): return "//ReactAndroid/src/main/" + path +def react_native_android_toplevel_dep(path): + return react_native_dep(path) + # Example: react_native_xplat_dep('java/com/facebook/systrace:systrace') def react_native_xplat_dep(path): return "//ReactCommon/" + path @@ -183,12 +186,18 @@ def rn_robolectric_test(name, srcs, vm_args = None, *args, **kwargs): is_androidx = kwargs.pop("is_androidx", False) + kwargs["deps"] = kwargs.pop("deps", []) + [ + react_native_android_toplevel_dep("third-party/java/mockito2:mockito2"), + react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock2"), + react_native_dep("third-party/java/robolectric/4.3.1:robolectric"), + ] + extra_vm_args = [ "-XX:+UseConcMarkSweepGC", # required by -XX:+CMSClassUnloadingEnabled "-XX:+CMSClassUnloadingEnabled", "-XX:ReservedCodeCacheSize=150M", - "-Drobolectric.dependency.dir=buck-out/gen/ReactAndroid/src/main/third-party/java/robolectric3/robolectric", - "-Dlibraries=buck-out/gen/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/*.jar", + "-Drobolectric.dependency.dir=buck-out/gen/ReactAndroid/src/main/third-party/java/robolectric/4.3.1", + "-Dlibraries=buck-out/gen/ReactAndroid/src/main/third-party/java/robolectric/4.3.1/*.jar", "-Drobolectric.logging.enabled=true", "-XX:MaxPermSize=620m", "-Drobolectric.offline=true", diff --git a/yarn.lock b/yarn.lock index 1489a15c9d62ee..4200c81b4d5580 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,18 +10,18 @@ "@babel/highlight" "^7.8.3" "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.4.5", "@babel/core@^7.7.5": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.0.tgz#a6fe5db77ebfb61e0da6c5c36aaf14aab07b2b44" + integrity sha512-FGgV2XyPoVtYDvbFXlukEWt13Afka4mBRQ2CoTsHxpgVGO6XfgtT6eI+WyjQRGGTL90IDkIVmme8riFCLZ8lUw== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" + "@babel/generator" "^7.10.0" "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/helpers" "^7.10.0" + "@babel/parser" "^7.10.0" + "@babel/template" "^7.10.0" + "@babel/traverse" "^7.10.0" + "@babel/types" "^7.10.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -31,22 +31,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.5.0", "@babel/generator@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.4.tgz#12441e90c3b3c4159cdecf312075bf1a8ce2dbce" - integrity sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA== - dependencies: - "@babel/types" "^7.9.0" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/generator@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" - integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== +"@babel/generator@^7.10.0", "@babel/generator@^7.5.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.0.tgz#a238837896edf35ee5fbfb074548d3256b4bc55d" + integrity sha512-ThoWCJHlgukbtCP79nAK4oLqZt5fVo70AHUni/y8Jotyg5rtJiG2FVl+iJjRNKIyl4hppqztLyAoEWcCvqyOFQ== dependencies: - "@babel/types" "^7.9.5" + "@babel/types" "^7.10.0" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -67,13 +57,13 @@ "@babel/types" "^7.8.3" "@babel/helper-builder-react-jsx-experimental@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz#066d80262ade488f9c1b1823ce5db88a4cedaa43" - integrity sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ== + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz#0b4b3e04e6123f03b404ca4dfd6528fe6bb92fe3" + integrity sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg== dependencies: "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-module-imports" "^7.8.3" - "@babel/types" "^7.9.0" + "@babel/types" "^7.9.5" "@babel/helper-builder-react-jsx@^7.9.0": version "7.9.0" @@ -83,16 +73,16 @@ "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/types" "^7.9.0" -"@babel/helper-create-class-features-plugin@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0" - integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg== +"@babel/helper-create-class-features-plugin@^7.10.0", "@babel/helper-create-class-features-plugin@^7.8.3": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.0.tgz#3a2b7b86f6365ea4ac3837a49ec5791e65217944" + integrity sha512-n4tPJaI0iuLRayriXTQ8brP3fMA/fNmxpxswfNuhe4qXQbcCWzeAqm6SeR/KExIOcdCvOh/KkPQVgBsjcb0oqA== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-function-name" "^7.9.5" + "@babel/helper-member-expression-to-functions" "^7.10.0" + "@babel/helper-optimise-call-expression" "^7.10.0" "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-replace-supers" "^7.10.0" "@babel/helper-split-export-declaration" "^7.8.3" "@babel/helper-create-regexp-features-plugin@^7.8.3": @@ -121,16 +111,7 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" - integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-function-name@^7.9.5": +"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== @@ -146,12 +127,12 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-member-expression-to-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" - integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== +"@babel/helper-member-expression-to-functions@^7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.0.tgz#e8cf57470bfd1247f2b41aa621a527e952efa6f1" + integrity sha512-xKLTpbMkJcvwEsDaTfs9h0IlfUiBLPFfybxaPpPPsQDsZTRg+UKh+86oK7sctHF3OUiRQkb10oS9MXSqgyV6/g== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.0" "@babel/helper-module-imports@^7.8.3": version "7.8.3" @@ -173,12 +154,12 @@ "@babel/types" "^7.9.0" lodash "^4.17.13" -"@babel/helper-optimise-call-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" - integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== +"@babel/helper-optimise-call-expression@^7.10.0", "@babel/helper-optimise-call-expression@^7.8.3": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.0.tgz#6dcfb565842f43bed31b24f3e4277f18826e5e79" + integrity sha512-HgMd8QKA8wMJs5uK/DYKdyzJAEuGt1zyDp9wLMlMR6LitTQTHPUE+msC82ZsEDwq+U3/yHcIXIngRm9MS4IcIg== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.0" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.8.3" @@ -203,15 +184,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== +"@babel/helper-replace-supers@^7.10.0", "@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.0.tgz#26bc22ee1a35450934d2e2a9b27de10a22fac9d6" + integrity sha512-erl4iVeiANf14JszXP7b69bSrz3e3+qW09pVvEmTWwzRQEOoyb1WFlYCA8d/VjVZGYW8+nGpLh7swf9CifH5wg== dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/helper-member-expression-to-functions" "^7.10.0" + "@babel/helper-optimise-call-expression" "^7.10.0" + "@babel/traverse" "^7.10.0" + "@babel/types" "^7.10.0" "@babel/helper-simple-access@^7.8.3": version "7.8.3" @@ -228,12 +209,7 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-validator-identifier@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" - integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== - -"@babel/helper-validator-identifier@^7.9.5": +"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== @@ -248,14 +224,14 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== +"@babel/helpers@^7.10.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.0.tgz#634400a0977b8dcf7b311761a77ca94ed974b3b6" + integrity sha512-lQtFJoDZAGf/t2PgR6Z59Q2MwjvOGGsxZ0BAlsrgyDhKuMbe63EfbQmVmcLfyTBj8J4UtiadQimcotvYVg/kVQ== dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/template" "^7.10.0" + "@babel/traverse" "^7.10.0" + "@babel/types" "^7.10.0" "@babel/highlight@^7.8.3": version "7.9.0" @@ -266,10 +242,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.10.0", "@babel/parser@^7.7.5": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.0.tgz#8eca3e71a73dd562c5222376b08253436bb4995b" + integrity sha512-fnDUl1Uy2gThM4IFVW4ISNHqr3cJrCsRkSCasFgx0XDO9JcttDS5ytyBc4Cu4X1+fjoo3IVvFbRD6TeFlHJlEQ== "@babel/plugin-check-constants@^7.0.0-beta.38": version "7.0.0-beta.38" @@ -308,12 +284,13 @@ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" "@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" - integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.0.tgz#d27b0910b637f7c9d9a5629f2adcd04dc9ea4e69" + integrity sha512-DOD+4TqMcRKJdAfN08+v9cciK5d0HW5hwTndOoKZEfEzU/mRrKboheD5mnWU4Q96VOnDdAj86kKjZhoQyG6s+A== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.9.5" "@babel/plugin-proposal-optional-catch-binding@^7.0.0": version "7.8.3" @@ -324,9 +301,9 @@ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.1.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.0.tgz#f9bdcd5cbf2e3037674903a45e56ed0cbaea1550" + integrity sha512-bn+9XT8Y6FJCO37ewj4E1gIirR35nDm+mGcqQV4dM3LKSVp3QTAU3f65Z0ld4y6jdfAlv2VKzCh4mezhRnl+6Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.0" @@ -460,21 +437,21 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.0.tgz#5d7aa0cf921ec91bdc97c9b311bf1fce0ea979b0" + integrity sha512-AoMn0D3nLG9i71useuBrZZTnHbjnhcaTXCckUtOx3JPuhGGJdOUYMwOV9niPJ+nZCk52dfLLqbmV3pBMCRQLNw== dependencies: "@babel/helper-plugin-utils" "^7.8.3" lodash "^4.17.13" "@babel/plugin-transform-classes@^7.0.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" - integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" + integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg== dependencies: "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" + "@babel/helper-function-name" "^7.9.5" "@babel/helper-optimise-call-expression" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.6" @@ -489,9 +466,9 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" - integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.0.tgz#59145194029721e49e511afb4bdd1d2f38369180" + integrity sha512-yKoghHpYbC0eM+6o6arPUJT9BQBvOOn8iOCEHwFvkJ5gjAxYmoUaAuLwaoA9h2YvC6dzcRI0KPQOpRXr8qQTxQ== dependencies: "@babel/helper-plugin-utils" "^7.8.3" @@ -512,9 +489,9 @@ "@babel/plugin-syntax-flow" "^7.8.3" "@babel/plugin-transform-for-of@^7.0.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" - integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.0.tgz#ff2bf95dc1deb9b309c7fd78d9620ac9266a3efe" + integrity sha512-0ldl5xEe9kbuhB1cDqs17JiBPEm1+6/LH7loo29+MAJOyB/xbpLI/u6mRzDPjr0nYL7z0S14FPT4hs2gH8Im9Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" @@ -540,17 +517,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-modules-commonjs@^7.0.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-commonjs@^7.1.0": +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" integrity sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ== @@ -575,10 +542,10 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.3" -"@babel/plugin-transform-parameters@^7.0.0": - version "7.9.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a" - integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" + integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA== dependencies: "@babel/helper-get-function-arity" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -606,9 +573,9 @@ "@babel/plugin-syntax-jsx" "^7.8.3" "@babel/plugin-transform-react-jsx-source@^7.0.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz#89ef93025240dd5d17d3122294a093e5e0183de0" - integrity sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.0.tgz#0e24978505130a79bb8ee1af15a1a7d8e783347d" + integrity sha512-EmUZ2YYXK6YFIdSxUJ1thg7gIBMHSEp8nGS6GwkXGpGdplpmOhj6azYjszT8YcFt6HyPElycDOd2lXckzN+OEw== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3" @@ -631,9 +598,9 @@ regenerator-transform "^0.14.2" "@babel/plugin-transform-runtime@^7.0.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.0.tgz#16e50ba682aa9925b94123a622d996cadd4cbef7" + integrity sha512-SWIc5IJnoLHk9qVRvvpebUW5lafStcKlLcqELMiNOApVIxPbCtkQfLRMCdaEKw4X92JItFKdoBxv2udiyGwFtg== dependencies: "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -648,9 +615,9 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-spread@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.0.tgz#6918d9b2b52c604802bd50a5f22b649efddf9af6" + integrity sha512-P3Zj04ylqumJBjmjylNl05ZHRo4j4gFNG7P70loys0//q5BTe30E8xIj6PnqEWAfsPYu2sdIPcJeeQdclqlM6A== dependencies: "@babel/helper-plugin-utils" "^7.8.3" @@ -671,11 +638,11 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-typescript@^7.5.0", "@babel/plugin-transform-typescript@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz#4bb4dde4f10bbf2d787fce9707fb09b483e33359" - integrity sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.0.tgz#00273cddb1f5321af09db5c096bb865eab137124" + integrity sha512-BGH4yn+QwYFfzh8ITmChwrcvhLf+jaYBlz+T87CNKTP49SbqrjqTsMqtFijivYWYjcYHvac8II53RYd82vRaAw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" + "@babel/helper-create-class-features-plugin" "^7.10.0" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-typescript" "^7.8.3" @@ -715,73 +682,40 @@ source-map-support "^0.5.16" "@babel/runtime@^7.0.0", "@babel/runtime@^7.8.4": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.0.tgz#2cdcd6d7a391c24f7154235134c830cfb58ac0b1" + integrity sha512-tgYb3zVApHbLHYOPWtVwg25sBqHhfBXRKeKoTIyoheIxln1nA7oBl7SfHfiTG2GhDPI8EUBkOD/0wJCP/3HN4Q== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.0.0", "@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== +"@babel/template@^7.0.0", "@babel/template@^7.10.0", "@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.0.tgz#f15d852ce16cd5fb3e219097a75f662710b249b1" + integrity sha512-aMLEQn5tcG49LEWrsEwxiRTdaJmvLem3+JMCMSeCy2TILau0IDVyWdm/18ACx7XOCady64FLt6KkHy28tkDQHQ== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/parser" "^7.10.0" + "@babel/types" "^7.10.0" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892" - integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.0.tgz#290935529881baf619398d94fd453838bef36740" + integrity sha512-NZsFleMaLF1zX3NxbtXI/JCs2RPOdpGru6UBdGsfhdsDsP+kFF+h2QQJnMJglxk0kc69YmMFs4A44OJY0tKo5g== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.7.4": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" + "@babel/generator" "^7.10.0" "@babel/helper-function-name" "^7.9.5" "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" + "@babel/parser" "^7.10.0" + "@babel/types" "^7.10.0" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5" - integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng== - dependencies: - "@babel/helper-validator-identifier" "^7.9.0" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.3.3": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" - integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== - dependencies: - "@babel/helper-validator-identifier" "^7.9.5" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== +"@babel/types@^7.0.0", "@babel/types@^7.10.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.8.3", "@babel/types@^7.9.0", "@babel/types@^7.9.5": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.0.tgz#d47d92249e42393a5723aad5319035ae411e3e38" + integrity sha512-t41W8yWFyQFPOAAvPvjyRhejcLGnJTA3iRpFcDbEKwVJ3UnHQePFzLk8GagTsucJlImyNwrGikGsYURrWbQG8w== dependencies: "@babel/helper-validator-identifier" "^7.9.5" lodash "^4.17.13" @@ -1743,13 +1677,6 @@ babel-jest@^26.0.1: graceful-fs "^4.2.4" slash "^3.0.0" -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== - dependencies: - object.assign "^4.1.0" - babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" @@ -3350,10 +3277,10 @@ flat-cache@^1.2.1: rimraf "~2.6.2" write "^0.2.1" -flow-bin@^0.124.0: - version "0.124.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.124.0.tgz#24b2e55874e1e2041f9247f42473b3db2ef32758" - integrity sha512-KEtDJ7CFUjcuhw6N52FTZshDd1krf1fxpp4APSIrwhVm+IrlcKJ+EMXpeXKM1kKNSZ347dYGh8wEvXQl4pHZEA== +flow-bin@^0.125.1: + version "0.125.1" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.125.1.tgz#7edbc71e7dc39ddef18086ef75c714bbf1c5917f" + integrity sha512-jEury9NTXylxQEOAXLWEE945BjBwYcMwwKVnb+5XORNwMQE7i5hQYF0ysYfsaaYOa7rW/U16rHBfwLuaZfWV7A== flow-parser@0.*: version "0.89.0" @@ -6248,6 +6175,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +qs@^6.5.1: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"