From 34c4fdb8e2adb12f0e9f536d55dd7fd6689ceeea Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 15 Dec 2021 07:18:05 -0800 Subject: [PATCH 1/4] Remove implicity type conversions from LayoutAnimations Summary: changelog: [internal] Reviewed By: philIip Differential Revision: D33058920 fbshipit-source-id: 6d39e26c369dad409f5141dceae7554fe65daaba --- .../react/renderer/animations/LayoutAnimationDriver.cpp | 6 +++--- .../animations/LayoutAnimationKeyFrameManager.cpp | 6 +++--- .../renderer/animations/LayoutAnimationKeyFrameManager.h | 2 +- ReactCommon/react/renderer/animations/conversions.h | 8 ++++---- ReactCommon/react/renderer/animations/primitives.h | 5 +++-- ReactCommon/react/renderer/animations/utils.cpp | 6 +++--- ReactCommon/react/renderer/animations/utils.h | 3 ++- ReactCommon/react/renderer/core/ComponentDescriptor.h | 3 ++- .../react/renderer/core/ConcreteComponentDescriptor.h | 3 ++- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp index 8933bbe2c5..4ab284b81c 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp @@ -47,10 +47,10 @@ void LayoutAnimationDriver::animationMutationsForFrame( : layoutAnimationConfig.updateConfig)); // Interpolate - std::pair progress = + auto progress = calculateAnimationProgress(now, animation, mutationConfig); - double animationTimeProgressLinear = progress.first; - double animationInterpolationFactor = progress.second; + auto animationTimeProgressLinear = progress.first; + auto animationInterpolationFactor = progress.second; auto mutatedShadowView = createInterpolatedShadowView( animationInterpolationFactor, baselineShadowView, finalShadowView); diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp index 13e2f11155..86689c96ee 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp @@ -85,8 +85,8 @@ void PrintMutationInstructionRelative( } #endif -static inline float -interpolateFloats(float coefficient, float oldValue, float newValue) { +static inline Float +interpolateFloats(Float coefficient, Float oldValue, Float newValue) { return oldValue + (newValue - oldValue) * coefficient; } @@ -1114,7 +1114,7 @@ LayoutAnimationKeyFrameManager::getComponentDescriptorForShadowView( } ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView( - double progress, + Float progress, ShadowView const &startingView, ShadowView const &finalView) const { react_native_assert(startingView.tag > 0); diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h index 528fe052da..970b1e3107 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h @@ -120,7 +120,7 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, * @return */ ShadowView createInterpolatedShadowView( - double progress, + Float progress, ShadowView const &startingView, ShadowView const &finalView) const; diff --git a/ReactCommon/react/renderer/animations/conversions.h b/ReactCommon/react/renderer/animations/conversions.h index 9fdb7a5e08..1dc6acf6ae 100644 --- a/ReactCommon/react/renderer/animations/conversions.h +++ b/ReactCommon/react/renderer/animations/conversions.h @@ -115,8 +115,8 @@ static inline better::optional parseAnimationConfig( double duration = defaultDuration; double delay = 0; - double springDamping = 0.5; - double initialVelocity = 0; + Float springDamping = 0.5; + Float initialVelocity = 0; auto const durationIt = config.find("duration"); if (durationIt != config.items().end()) { @@ -144,7 +144,7 @@ static inline better::optional parseAnimationConfig( if (springDampingIt != config.items().end() && springDampingIt->second.isDouble()) { if (springDampingIt->second.isDouble()) { - springDamping = springDampingIt->second.asDouble(); + springDamping = (Float)springDampingIt->second.asDouble(); } else { LOG(ERROR) << "Error parsing animation config: field `springDamping` must be a number"; @@ -155,7 +155,7 @@ static inline better::optional parseAnimationConfig( auto const initialVelocityIt = config.find("initialVelocity"); if (initialVelocityIt != config.items().end()) { if (initialVelocityIt->second.isDouble()) { - initialVelocity = initialVelocityIt->second.asDouble(); + initialVelocity = (Float)initialVelocityIt->second.asDouble(); } else { LOG(ERROR) << "Error parsing animation config: field `initialVelocity` must be a number"; diff --git a/ReactCommon/react/renderer/animations/primitives.h b/ReactCommon/react/renderer/animations/primitives.h index 757c72c466..d204eeeb55 100644 --- a/ReactCommon/react/renderer/animations/primitives.h +++ b/ReactCommon/react/renderer/animations/primitives.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -43,8 +44,8 @@ struct AnimationConfig { 0; // these are perhaps better represented as uint64_t, but they // come from JS as doubles double delay = 0; - double springDamping = 0; - double initialVelocity = 0; + Float springDamping = 0; + Float initialVelocity = 0; }; // This corresponds exactly with JS. diff --git a/ReactCommon/react/renderer/animations/utils.cpp b/ReactCommon/react/renderer/animations/utils.cpp index 4d12eeab96..5bb6a19867 100644 --- a/ReactCommon/react/renderer/animations/utils.cpp +++ b/ReactCommon/react/renderer/animations/utils.cpp @@ -11,7 +11,7 @@ namespace facebook { namespace react { -std::pair calculateAnimationProgress( +std::pair calculateAnimationProgress( uint64_t now, LayoutAnimation const &animation, AnimationConfig const &mutationConfig) { @@ -20,8 +20,8 @@ std::pair calculateAnimationProgress( } uint64_t startTime = animation.startTime; - uint64_t delay = mutationConfig.delay; - uint64_t endTime = startTime + delay + mutationConfig.duration; + uint64_t delay = (uint64_t)mutationConfig.delay; + uint64_t endTime = startTime + delay + (uint64_t)mutationConfig.duration; if (now >= endTime) { return {1, 1}; diff --git a/ReactCommon/react/renderer/animations/utils.h b/ReactCommon/react/renderer/animations/utils.h index 8654cea0a4..6252732a02 100644 --- a/ReactCommon/react/renderer/animations/utils.h +++ b/ReactCommon/react/renderer/animations/utils.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include namespace facebook { @@ -71,7 +72,7 @@ static inline bool shouldFirstComeBeforeSecondMutation( return false; } -std::pair calculateAnimationProgress( +std::pair calculateAnimationProgress( uint64_t now, LayoutAnimation const &animation, AnimationConfig const &mutationConfig); diff --git a/ReactCommon/react/renderer/core/ComponentDescriptor.h b/ReactCommon/react/renderer/core/ComponentDescriptor.h index e0bac0845e..b46f00823c 100644 --- a/ReactCommon/react/renderer/core/ComponentDescriptor.h +++ b/ReactCommon/react/renderer/core/ComponentDescriptor.h @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace facebook { @@ -112,7 +113,7 @@ class ComponentDescriptor { */ virtual SharedProps interpolateProps( const PropsParserContext &context, - float animationProgress, + Float animationProgress, const SharedProps &props, const SharedProps &newProps) const = 0; diff --git a/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index 08193e7609..2367297e3b 100644 --- a/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace facebook { namespace react { @@ -113,7 +114,7 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { SharedProps interpolateProps( const PropsParserContext &context, - float animationProgress, + Float animationProgress, const SharedProps &props, const SharedProps &newProps) const override { #ifdef ANDROID From d902a89b40ad94549d84fb8725ef0788e5dbddf3 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 15 Dec 2021 10:50:45 -0800 Subject: [PATCH 2/4] Move surface registry code out of UIManagerBinding Summary: changelog: [internal] Just moving code that doesn't belong to UIManagerBinding out of the class. Reviewed By: philIip Differential Revision: D33060412 fbshipit-source-id: 2d54929072cef14fd1fa6b70bde382ae21ecff45 --- .../uimanager/SurfaceRegistryBinding.cpp | 105 ++++++++++++ .../uimanager/SurfaceRegistryBinding.h | 49 ++++++ .../react/renderer/uimanager/UIManager.cpp | 22 +-- .../renderer/uimanager/UIManagerBinding.cpp | 154 +----------------- .../renderer/uimanager/UIManagerBinding.h | 29 ---- .../react/renderer/uimanager/bindingUtils.cpp | 79 +++++++++ .../react/renderer/uimanager/bindingUtils.h | 20 +++ packages/rn-tester/Podfile.lock | 8 +- 8 files changed, 263 insertions(+), 203 deletions(-) create mode 100644 ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.cpp create mode 100644 ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.h create mode 100644 ReactCommon/react/renderer/uimanager/bindingUtils.cpp create mode 100644 ReactCommon/react/renderer/uimanager/bindingUtils.h diff --git a/ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.cpp b/ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.cpp new file mode 100644 index 0000000000..129ee6597f --- /dev/null +++ b/ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.cpp @@ -0,0 +1,105 @@ +/* + * 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 "SurfaceRegistryBinding.h" +#include +#include +#include +#include "bindingUtils.h" + +namespace facebook::react { + +void SurfaceRegistryBinding::startSurface( + jsi::Runtime &runtime, + SurfaceId surfaceId, + std::string const &moduleName, + folly::dynamic const &initalProps, + DisplayMode displayMode) { + SystraceSection s("SurfaceRegistryBinding::startSurface"); + folly::dynamic parameters = folly::dynamic::object(); + parameters["rootTag"] = surfaceId; + parameters["initialProps"] = initalProps; + parameters["fabric"] = true; + + if (moduleName != "LogBox" && + runtime.global().hasProperty(runtime, "RN$SurfaceRegistry")) { + auto registry = + runtime.global().getPropertyAsObject(runtime, "RN$SurfaceRegistry"); + auto method = registry.getPropertyAsFunction(runtime, "renderSurface"); + + method.call( + runtime, + {jsi::String::createFromUtf8(runtime, moduleName), + jsi::valueFromDynamic(runtime, parameters), + jsi::Value(runtime, displayModeToInt(displayMode))}); + } else { + callMethodOfModule( + runtime, + "AppRegistry", + "runApplication", + {jsi::String::createFromUtf8(runtime, moduleName), + jsi::valueFromDynamic(runtime, parameters), + jsi::Value(runtime, displayModeToInt(displayMode))}); + } +} + +void SurfaceRegistryBinding::setSurfaceProps( + jsi::Runtime &runtime, + SurfaceId surfaceId, + std::string const &moduleName, + folly::dynamic const &initalProps, + DisplayMode displayMode) { + SystraceSection s("UIManagerBinding::setSurfaceProps"); + folly::dynamic parameters = folly::dynamic::object(); + parameters["rootTag"] = surfaceId; + parameters["initialProps"] = initalProps; + parameters["fabric"] = true; + + if (moduleName != "LogBox" && + runtime.global().hasProperty(runtime, "RN$SurfaceRegistry")) { + auto registry = + runtime.global().getPropertyAsObject(runtime, "RN$SurfaceRegistry"); + auto method = registry.getPropertyAsFunction(runtime, "setSurfaceProps"); + + method.call( + runtime, + {jsi::String::createFromUtf8(runtime, moduleName), + jsi::valueFromDynamic(runtime, parameters), + jsi::Value(runtime, displayModeToInt(displayMode))}); + } else { + callMethodOfModule( + runtime, + "AppRegistry", + "setSurfaceProps", + {jsi::String::createFromUtf8(runtime, moduleName), + jsi::valueFromDynamic(runtime, parameters), + jsi::Value(runtime, displayModeToInt(displayMode))}); + } +} + +void SurfaceRegistryBinding::stopSurface( + jsi::Runtime &runtime, + SurfaceId surfaceId) { + auto global = runtime.global(); + if (global.hasProperty(runtime, "RN$Bridgeless")) { + if (!global.hasProperty(runtime, "RN$stopSurface")) { + // ReactFabric module has not been loaded yet; there's no surface to stop. + return; + } + // Bridgeless mode uses a custom JSI binding instead of callable module. + global.getPropertyAsFunction(runtime, "RN$stopSurface") + .call(runtime, {jsi::Value{surfaceId}}); + } else { + callMethodOfModule( + runtime, + "ReactFabric", + "unmountComponentAtNode", + {jsi::Value{surfaceId}}); + } +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.h b/ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.h new file mode 100644 index 0000000000..9e71eb8f48 --- /dev/null +++ b/ReactCommon/react/renderer/uimanager/SurfaceRegistryBinding.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 + +namespace facebook::react { + +class SurfaceRegistryBinding final { + public: + SurfaceRegistryBinding() = delete; + + /* + * Starts React Native Surface with given id, moduleName, and props. + * Thread synchronization must be enforced externally. + */ + static void startSurface( + jsi::Runtime &runtime, + SurfaceId surfaceId, + std::string const &moduleName, + folly::dynamic const &initalProps, + DisplayMode displayMode); + + /* + * Updates the React Native Surface identified with surfaceId and moduleName + * with the given props. + * Thread synchronization must be enforced externally. + */ + static void setSurfaceProps( + jsi::Runtime &runtime, + SurfaceId surfaceId, + std::string const &moduleName, + folly::dynamic const &initalProps, + DisplayMode displayMode); + + /* + * Stops React Native Surface with given id. + * Thread synchronization must be enforced externally. + */ + static void stopSurface(jsi::Runtime &runtime, SurfaceId surfaceId); +}; + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/uimanager/UIManager.cpp b/ReactCommon/react/renderer/uimanager/UIManager.cpp index a91d73c610..4a279b0593 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -169,12 +170,7 @@ void UIManager::startSurface( runtimeExecutor_([=](jsi::Runtime &runtime) { SystraceSection s("UIManager::startSurface::onRuntime"); - auto uiManagerBinding = UIManagerBinding::getBinding(runtime); - if (!uiManagerBinding) { - return; - } - - uiManagerBinding->startSurface( + SurfaceRegistryBinding::startSurface( runtime, surfaceId, moduleName, props, displayMode); }); } @@ -187,12 +183,7 @@ void UIManager::setSurfaceProps( SystraceSection s("UIManager::setSurfaceProps"); runtimeExecutor_([=](jsi::Runtime &runtime) { - auto uiManagerBinding = UIManagerBinding::getBinding(runtime); - if (!uiManagerBinding) { - return; - } - - uiManagerBinding->setSurfaceProps( + SurfaceRegistryBinding::setSurfaceProps( runtime, surfaceId, moduleName, props, displayMode); }); } @@ -212,12 +203,7 @@ ShadowTree::Unique UIManager::stopSurface(SurfaceId surfaceId) const { // the JavaScript side will not be able to reference a `ShadowTree` and will // fail silently. runtimeExecutor_([=](jsi::Runtime &runtime) { - auto uiManagerBinding = UIManagerBinding::getBinding(runtime); - if (!uiManagerBinding) { - return; - } - - uiManagerBinding->stopSurface(runtime, surfaceId); + SurfaceRegistryBinding::stopSurface(runtime, surfaceId); }); if (leakChecker_) { diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index e9b675a899..4b314adf37 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -15,49 +15,10 @@ #include #include +#include "bindingUtils.h" + namespace facebook::react { -static jsi::Value getModule( - jsi::Runtime &runtime, - std::string const &moduleName) { - auto batchedBridge = - runtime.global().getPropertyAsObject(runtime, "__fbBatchedBridge"); - auto getCallableModule = - batchedBridge.getPropertyAsFunction(runtime, "getCallableModule"); - auto moduleAsValue = getCallableModule.callWithThis( - runtime, - batchedBridge, - {jsi::String::createFromUtf8(runtime, moduleName)}); - if (!moduleAsValue.isObject()) { - LOG(ERROR) << "getModule of " << moduleName << " is not an object"; - } - react_native_assert(moduleAsValue.isObject()); - return moduleAsValue; -} - -static bool checkBatchedBridgeIsActive(jsi::Runtime &runtime) { - if (!runtime.global().hasProperty(runtime, "__fbBatchedBridge")) { - LOG(ERROR) - << "getPropertyAsObject: property '__fbBatchedBridge' is undefined, expected an Object"; - return false; - } - return true; -} - -static bool checkGetCallableModuleIsActive(jsi::Runtime &runtime) { - if (!checkBatchedBridgeIsActive(runtime)) { - return false; - } - auto batchedBridge = - runtime.global().getPropertyAsObject(runtime, "__fbBatchedBridge"); - if (!batchedBridge.hasProperty(runtime, "getCallableModule")) { - LOG(ERROR) - << "getPropertyAsFunction: function 'getCallableModule' is undefined, expected a Function"; - return false; - } - return true; -} - void UIManagerBinding::createAndInstallIfNeeded( jsi::Runtime &runtime, RuntimeExecutor const &runtimeExecutor, @@ -101,29 +62,6 @@ UIManagerBinding::~UIManagerBinding() { << this << ")."; } -static jsi::Value callMethodOfModule( - jsi::Runtime &runtime, - std::string const &moduleName, - std::string const &methodName, - std::initializer_list args) { - if (checkGetCallableModuleIsActive(runtime)) { - auto module = getModule(runtime, moduleName); - if (module.isObject()) { - jsi::Object object = module.asObject(runtime); - react_native_assert(object.hasProperty(runtime, methodName.c_str())); - if (object.hasProperty(runtime, methodName.c_str())) { - auto method = object.getPropertyAsFunction(runtime, methodName.c_str()); - return method.callWithThis(runtime, object, args); - } else { - LOG(ERROR) << "getPropertyAsFunction: property '" << methodName - << "' is undefined, expected a Function"; - } - } - } - - return jsi::Value::undefined(); -} - jsi::Value UIManagerBinding::getInspectorDataForInstance( jsi::Runtime &runtime, EventEmitter const &eventEmitter) const { @@ -151,94 +89,6 @@ jsi::Value UIManagerBinding::getInspectorDataForInstance( {std::move(instanceHandle)}); } -void UIManagerBinding::startSurface( - jsi::Runtime &runtime, - SurfaceId surfaceId, - std::string const &moduleName, - folly::dynamic const &initalProps, - DisplayMode displayMode) const { - SystraceSection s("UIManagerBinding::startSurface"); - folly::dynamic parameters = folly::dynamic::object(); - parameters["rootTag"] = surfaceId; - parameters["initialProps"] = initalProps; - parameters["fabric"] = true; - - if (moduleName != "LogBox" && - runtime.global().hasProperty(runtime, "RN$SurfaceRegistry")) { - auto registry = - runtime.global().getPropertyAsObject(runtime, "RN$SurfaceRegistry"); - auto method = registry.getPropertyAsFunction(runtime, "renderSurface"); - - method.call( - runtime, - {jsi::String::createFromUtf8(runtime, moduleName), - jsi::valueFromDynamic(runtime, parameters), - jsi::Value(runtime, displayModeToInt(displayMode))}); - } else { - callMethodOfModule( - runtime, - "AppRegistry", - "runApplication", - {jsi::String::createFromUtf8(runtime, moduleName), - jsi::valueFromDynamic(runtime, parameters), - jsi::Value(runtime, displayModeToInt(displayMode))}); - } -} - -void UIManagerBinding::setSurfaceProps( - jsi::Runtime &runtime, - SurfaceId surfaceId, - std::string const &moduleName, - folly::dynamic const &initalProps, - DisplayMode displayMode) const { - SystraceSection s("UIManagerBinding::setSurfaceProps"); - folly::dynamic parameters = folly::dynamic::object(); - parameters["rootTag"] = surfaceId; - parameters["initialProps"] = initalProps; - parameters["fabric"] = true; - - if (moduleName != "LogBox" && - runtime.global().hasProperty(runtime, "RN$SurfaceRegistry")) { - auto registry = - runtime.global().getPropertyAsObject(runtime, "RN$SurfaceRegistry"); - auto method = registry.getPropertyAsFunction(runtime, "setSurfaceProps"); - - method.call( - runtime, - {jsi::String::createFromUtf8(runtime, moduleName), - jsi::valueFromDynamic(runtime, parameters), - jsi::Value(runtime, displayModeToInt(displayMode))}); - } else { - callMethodOfModule( - runtime, - "AppRegistry", - "setSurfaceProps", - {jsi::String::createFromUtf8(runtime, moduleName), - jsi::valueFromDynamic(runtime, parameters), - jsi::Value(runtime, displayModeToInt(displayMode))}); - } -} - -void UIManagerBinding::stopSurface(jsi::Runtime &runtime, SurfaceId surfaceId) - const { - auto global = runtime.global(); - if (global.hasProperty(runtime, "RN$Bridgeless")) { - if (!global.hasProperty(runtime, "RN$stopSurface")) { - // ReactFabric module has not been loaded yet; there's no surface to stop. - return; - } - // Bridgeless mode uses a custom JSI binding instead of callable module. - global.getPropertyAsFunction(runtime, "RN$stopSurface") - .call(runtime, {jsi::Value{surfaceId}}); - } else { - callMethodOfModule( - runtime, - "ReactFabric", - "unmountComponentAtNode", - {jsi::Value{surfaceId}}); - } -} - void UIManagerBinding::dispatchEvent( jsi::Runtime &runtime, EventTarget const *eventTarget, diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.h b/ReactCommon/react/renderer/uimanager/UIManagerBinding.h index e14b7e24cf..d93808ba1a 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.h +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.h @@ -43,39 +43,10 @@ class UIManagerBinding : public jsi::HostObject { ~UIManagerBinding(); - /* - * Starts React Native Surface with given id, moduleName, and props. - * Thread synchronization must be enforced externally. - */ - void startSurface( - jsi::Runtime &runtime, - SurfaceId surfaceId, - std::string const &moduleName, - folly::dynamic const &initalProps, - DisplayMode displayMode) const; - - /* - * Updates the React Native Surface identified with surfaceId and moduleName - * with the given props. - * Thread synchronization must be enforced externally. - */ - void setSurfaceProps( - jsi::Runtime &runtime, - SurfaceId surfaceId, - std::string const &moduleName, - folly::dynamic const &props, - DisplayMode displayMode) const; - jsi::Value getInspectorDataForInstance( jsi::Runtime &runtime, EventEmitter const &eventEmitter) const; - /* - * Stops React Native Surface with given id. - * Thread synchronization must be enforced externally. - */ - void stopSurface(jsi::Runtime &runtime, SurfaceId surfaceId) const; - /* * Delivers raw event data to JavaScript. * Thread synchronization must be enforced externally. diff --git a/ReactCommon/react/renderer/uimanager/bindingUtils.cpp b/ReactCommon/react/renderer/uimanager/bindingUtils.cpp new file mode 100644 index 0000000000..d98fab50c4 --- /dev/null +++ b/ReactCommon/react/renderer/uimanager/bindingUtils.cpp @@ -0,0 +1,79 @@ +/* + * 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 "bindingUtils.h" + +#include +#include + +namespace facebook::react { + +static jsi::Value getModule( + jsi::Runtime &runtime, + std::string const &moduleName) { + auto batchedBridge = + runtime.global().getPropertyAsObject(runtime, "__fbBatchedBridge"); + auto getCallableModule = + batchedBridge.getPropertyAsFunction(runtime, "getCallableModule"); + auto moduleAsValue = getCallableModule.callWithThis( + runtime, + batchedBridge, + {jsi::String::createFromUtf8(runtime, moduleName)}); + if (!moduleAsValue.isObject()) { + LOG(ERROR) << "getModule of " << moduleName << " is not an object"; + } + react_native_assert(moduleAsValue.isObject()); + return moduleAsValue; +} + +static bool checkBatchedBridgeIsActive(jsi::Runtime &runtime) { + if (!runtime.global().hasProperty(runtime, "__fbBatchedBridge")) { + LOG(ERROR) + << "getPropertyAsObject: property '__fbBatchedBridge' is undefined, expected an Object"; + return false; + } + return true; +} + +static bool checkGetCallableModuleIsActive(jsi::Runtime &runtime) { + if (!checkBatchedBridgeIsActive(runtime)) { + return false; + } + auto batchedBridge = + runtime.global().getPropertyAsObject(runtime, "__fbBatchedBridge"); + if (!batchedBridge.hasProperty(runtime, "getCallableModule")) { + LOG(ERROR) + << "getPropertyAsFunction: function 'getCallableModule' is undefined, expected a Function"; + return false; + } + return true; +} + +jsi::Value callMethodOfModule( + jsi::Runtime &runtime, + std::string const &moduleName, + std::string const &methodName, + std::initializer_list args) { + if (checkGetCallableModuleIsActive(runtime)) { + auto module = getModule(runtime, moduleName); + if (module.isObject()) { + jsi::Object object = module.asObject(runtime); + react_native_assert(object.hasProperty(runtime, methodName.c_str())); + if (object.hasProperty(runtime, methodName.c_str())) { + auto method = object.getPropertyAsFunction(runtime, methodName.c_str()); + return method.callWithThis(runtime, object, args); + } else { + LOG(ERROR) << "getPropertyAsFunction: property '" << methodName + << "' is undefined, expected a Function"; + } + } + } + + return jsi::Value::undefined(); +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/uimanager/bindingUtils.h b/ReactCommon/react/renderer/uimanager/bindingUtils.h new file mode 100644 index 0000000000..e32854924b --- /dev/null +++ b/ReactCommon/react/renderer/uimanager/bindingUtils.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 + +namespace facebook::react { + +jsi::Value callMethodOfModule( + jsi::Runtime &runtime, + std::string const &moduleName, + std::string const &methodName, + std::initializer_list args); + +} // namespace facebook::react diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 898d8d5b85..5bc0b30a0a 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -881,7 +881,7 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 FBLazyVector: b81a2b70c72d8b0aefb652cea22c11e9ffd02949 - FBReactNativeSpec: 02b0c2bc3cf30c9ab1d5af08c22b5573bc6af06c + FBReactNativeSpec: 140e95aa3cc1f6084938f6b8123bb0c4398325d1 Flipper: 30e8eeeed6abdc98edaf32af0cda2f198be4b733 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 57ffbe81ef95306cc9e69c4aa3aeeeeb58a6a28c @@ -901,7 +901,7 @@ SPEC CHECKSUMS: React: f64c9f6db5428717922a3292ba6a448615a2e143 React-callinvoker: c5d61e29df57793f0dc10ec2bc01c846f863e51f React-Codegen: 2256e335ccce7326eeca6d7a668e05c4de259289 - React-Core: 22bc86b79dd931dbfb7fd2af91a35a98d41ceb64 + React-Core: 50d95abfb3c60d95e33a4b74e05e2a414bea4b73 React-CoreModules: a8e2bdc1ebbf8d440478456197abd58d1691f61a React-cxxreact: cfc1663dae1ea52b465bbf021ef7b1527c5dc80c React-Fabric: 002345cff43721617e0a3c0866f6f76bae8c50ff @@ -923,10 +923,10 @@ SPEC CHECKSUMS: React-RCTTest: 12bbd7fc2e72bd9920dc7286c5b8ef96639582b6 React-RCTText: e9146b2c0550a83d1335bfe2553760070a2d75c7 React-RCTVibration: 50be9c390f2da76045ef0dfdefa18b9cf9f35cfa - React-rncore: 5293698ae0222ddbaf47ce6193591b5fe3cb826c + React-rncore: 90798d1c013f1c62d0066c5618ddf8681c7a7c22 React-runtimeexecutor: 4b0c6eb341c7d3ceb5e2385cb0fdb9bf701024f3 ReactCommon: 7a2714d1128f965392b6f99a8b390e3aa38c9569 - ScreenshotManager: 99830e1bd3d2b595ef092bb20fe9bb644deb9082 + ScreenshotManager: ccf2b410b3c59385b26b8460a58cca8a8aca5e85 Yoga: c0d06f5380d34e939f55420669a60fe08b79bd75 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a From f16cbe59ef4dde2ec4cac42739aef64385426f54 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 15 Dec 2021 11:41:37 -0800 Subject: [PATCH 3/4] Add missing scripts/packager-reporter.js file in npm published files (#32763) Summary: When using the nightly builds, metro fails with an error because of missing scripts/packager-reporter.js. The file is not included in the published files so this is why. ## Changelog [Internal] [Fixed] - Add missing scripts/packager-reporter.js file in npm published files Pull Request resolved: https://github.com/facebook/react-native/pull/32763 Test Plan: Test that nightly builds work when adding the missing file. Reviewed By: motiz88 Differential Revision: D33128315 Pulled By: ShikaSD fbshipit-source-id: 617d124205edf63dbca8da50336ca895f5ce866a --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7be34994bc..86372cc3b6 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "scripts/launchPackager.command", "scripts/node-binary.sh", "scripts/packager.sh", + "scripts/packager-reporter.js", "scripts/react_native_pods.rb", "scripts/react-native-xcode.sh", "template.config.js", From 19174a5ec5fc4343b9a851d5fd9996efa0d0a8f8 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Wed, 15 Dec 2021 12:52:50 -0800 Subject: [PATCH 4/4] Touch emitter tests (#32768) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/32768 Adds tests for Fabric event tranformations into payload. For now, it tests the new event processing exclusively for sanity checks, but running code removed in D32953664 (https://github.com/facebook/react-native/commit/3b6d8af2908985c5be6200319d82c5594c22d889) produces the same results. Changelog: [Internal] Reviewed By: mdvacca, sshic Differential Revision: D33070156 fbshipit-source-id: 86af725373c20d74e17a63773c4c3c9138e1e20e --- .../com/facebook/react/fabric/events/BUCK | 16 + .../fabric/events/TouchEventDispatchTest.java | 616 ++++++++++++++++++ 2 files changed, 632 insertions(+) create mode 100644 ReactAndroid/src/test/java/com/facebook/react/fabric/events/BUCK create mode 100644 ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java diff --git a/ReactAndroid/src/test/java/com/facebook/react/fabric/events/BUCK b/ReactAndroid/src/test/java/com/facebook/react/fabric/events/BUCK new file mode 100644 index 0000000000..f1cf0a0ff9 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/fabric/events/BUCK @@ -0,0 +1,16 @@ +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_robolectric_test") + +rn_robolectric_test( + name = "events", + srcs = glob(["*.java"]), + contacts = ["oncall+react_native@xmail.facebook.com"], + deps = [ + react_native_dep("third-party/java/jsr-305:jsr-305"), + react_native_dep("third-party/java/junit:junit"), + 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"), + react_native_target("java/com/facebook/react/touch:touch"), + react_native_target("java/com/facebook/react/fabric:fabric"), + ], +) diff --git a/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java b/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java new file mode 100644 index 0000000000..10f1390a22 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java @@ -0,0 +1,616 @@ +/* + * 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.events; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.powermock.api.mockito.PowerMockito.doAnswer; +import static org.powermock.api.mockito.PowerMockito.mock; + +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.JavaOnlyArray; +import com.facebook.react.bridge.JavaOnlyMap; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.fabric.FabricUIManager; +import com.facebook.react.uimanager.DisplayMetricsHolder; +import com.facebook.react.uimanager.events.TouchEvent; +import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; +import com.facebook.react.uimanager.events.TouchEventType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RobolectricTestRunner; + +@PrepareForTest({Arguments.class, FabricUIManager.class}) +@SuppressStaticInitializationFor("com.facebook.react.fabric.FabricUIManager") +@RunWith(RobolectricTestRunner.class) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) +public class TouchEventDispatchTest { + + private static final int SURFACE_ID = 121; + private static final int TARGET_VIEW_ID = 42; + private static final int GESTURE_START_TIME = 1; + + @Rule public PowerMockRule rule = new PowerMockRule(); + + private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper = + new TouchEventCoalescingKeyHelper(); + + /** Events (1 pointer): START -> MOVE -> MOVE -> UP */ + private final TouchEvent[] mStartMoveEndSequence = + new TouchEvent[] { + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_DOWN, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_MOVE, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 2f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_MOVE, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 3f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_UP, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 3f)}) + }; + + /** Expected values for {@link #mStartMoveEndSequence} */ + private final List mStartMoveEndExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 1f, + GESTURE_START_TIME, + 0, + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * MOVE event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 2f, + GESTURE_START_TIME, + 0, + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0))), + /* + * MOVE event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 3f, + GESTURE_START_TIME, + 0, + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0))), + /* + * END event for touch 1: + * { + * touches: [], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 3f, + GESTURE_START_TIME, + 0, + Collections.emptyList(), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)))); + + /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> UP 2st -> UP 1st */ + private final TouchEvent[] mStartPointerMoveUpSequence = + new TouchEvent[] { + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_DOWN, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_POINTER_DOWN, + 1, + new int[] {0, 1}, + new PointerCoords[] {pointerCoords(1f, 1f), pointerCoords(2f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_MOVE, + 0, + new int[] {0, 1}, + new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_POINTER_UP, + 1, + new int[] {0, 1}, + new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_POINTER_UP, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 2f)}) + }; + + /** Expected values for {@link #mStartPointerMoveUpSequence} */ + private final List mStartPointerMoveUpExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touch: 0, + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 1f, + GESTURE_START_TIME, + 0, + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * START event for touch 2: + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * MOVE event for touch 1: + * { + * touch: 0, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 2f, + GESTURE_START_TIME, + 0, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * UP event pointer 1: + * { + * touch: 1, + * touches: [touch0], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * UP event pointer 0: + * { + * touch: 0, + * touches: [], + * changed: [touch0] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 2f, + GESTURE_START_TIME, + 0, + Collections.emptyList(), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)))); + + /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> CANCEL */ + private final TouchEvent[] mStartMoveCancelSequence = + new TouchEvent[] { + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_DOWN, + 0, + new int[] {0}, + new PointerCoords[] {pointerCoords(1f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_POINTER_DOWN, + 1, + new int[] {0, 1}, + new PointerCoords[] {pointerCoords(1f, 1f), pointerCoords(2f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_MOVE, + 0, + new int[] {0, 1}, + new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), + createTouchEvent( + GESTURE_START_TIME, + MotionEvent.ACTION_CANCEL, + 0, + new int[] {0, 1}, + new PointerCoords[] {pointerCoords(1f, 3f), pointerCoords(2f, 1f)}) + }; + + /** Expected values for {@link #mStartMoveCancelSequence} */ + private final List mStartMoveCancelExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touch: 0, + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 1f, + GESTURE_START_TIME, + 0, + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * START event for touch 2: + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * MOVE event for touch 1: + * { + * touch: 0, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 2f, + GESTURE_START_TIME, + 0, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * CANCEL event: + * { + * touch: 0, + * touches: [], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 1f, + 3f, + GESTURE_START_TIME, + 0, + Collections.emptyList(), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + Collections.emptyList(), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)))); + + List mDispatchedEvents; + FabricEventEmitter mEventEmitter; + + @Before + public void setUp() { + PowerMockito.mockStatic(Arguments.class); + PowerMockito.mockStatic(FabricUIManager.class); + PowerMockito.when(Arguments.createArray()) + .thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + return new JavaOnlyArray(); + } + }); + PowerMockito.when(Arguments.createMap()) + .thenAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + return new JavaOnlyMap(); + } + }); + + DisplayMetrics metrics = new DisplayMetrics(); + metrics.xdpi = 1f; + metrics.ydpi = 1f; + metrics.density = 1f; + DisplayMetricsHolder.setWindowDisplayMetrics(metrics); + + FabricUIManager fabricUIManager = mock(FabricUIManager.class); + mDispatchedEvents = new ArrayList<>(); + doAnswer( + new Answer() { + @Override + public Void answer(InvocationOnMock invocation) { + mDispatchedEvents.add(invocation.getArgument(5)); + return null; + } + }) + .when(fabricUIManager) + .receiveEvent( + anyInt(), + anyInt(), + anyString(), + anyBoolean(), + anyInt(), + ArgumentMatchers.any(), + anyInt()); + mEventEmitter = new FabricEventEmitter(fabricUIManager); + } + + @Test + public void testFabric_startMoveEnd() { + for (TouchEvent event : mStartMoveEndSequence) { + event.dispatchModern(mEventEmitter); + } + + assertEquals(mStartMoveEndExpectedSequence, mDispatchedEvents); + } + + @Test + public void testFabric_startMoveCancel() { + for (TouchEvent event : mStartMoveCancelSequence) { + event.dispatchModern(mEventEmitter); + } + + assertEquals(mStartMoveCancelExpectedSequence, mDispatchedEvents); + } + + @Test + public void testFabric_startPointerUpCancel() { + for (TouchEvent event : mStartPointerMoveUpSequence) { + event.dispatchModern(mEventEmitter); + } + + assertEquals(mStartPointerMoveUpExpectedSequence, mDispatchedEvents); + } + + private TouchEvent createTouchEvent( + int gestureTime, int action, int pointerId, int[] pointerIds, PointerCoords[] pointerCoords) { + mTouchEventCoalescingKeyHelper.addCoalescingKey(gestureTime); + action |= pointerId << MotionEvent.ACTION_POINTER_INDEX_SHIFT; + return TouchEvent.obtain( + SURFACE_ID, + TARGET_VIEW_ID, + getType(action), + MotionEvent.obtain( + gestureTime, + gestureTime, + action, + pointerIds.length, + pointerIds, + pointerCoords, + 0, + 0f, + 0f, + 0, + 0, + 0, + 0), + gestureTime, + pointerCoords[0].x, + pointerCoords[0].y, + mTouchEventCoalescingKeyHelper); + } + + private static TouchEventType getType(int action) { + action &= ~MotionEvent.ACTION_POINTER_INDEX_MASK; + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + return TouchEventType.START; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + return TouchEventType.END; + case MotionEvent.ACTION_MOVE: + return TouchEventType.MOVE; + case MotionEvent.ACTION_CANCEL: + return TouchEventType.CANCEL; + } + + return TouchEventType.START; + } + + private static ReadableMap buildGestureEvent( + int surfaceId, + int viewTag, + float locationX, + float locationY, + int time, + int pointerId, + List touches, + List changedTouches) { + WritableMap gesture = buildGesture(surfaceId, viewTag, locationX, locationY, time, pointerId); + gesture.putArray("changedTouches", JavaOnlyArray.from(changedTouches)); + gesture.putArray("touches", JavaOnlyArray.from(touches)); + return gesture; + } + + private static WritableMap buildGesture( + int surfaceId, int viewTag, float locationX, float locationY, int time, int pointerId) { + WritableMap map = new JavaOnlyMap(); + map.putInt("targetSurface", surfaceId); + map.putInt("target", viewTag); + map.putDouble("locationX", locationX); + map.putDouble("locationY", locationY); + map.putDouble("pageX", locationX); + map.putDouble("pageY", locationY); + map.putDouble("identifier", pointerId); + map.putDouble("timestamp", time); + return map; + } + + @SafeVarargs + private static List listOf(E... args) { + return Arrays.asList(args); + } + + private static PointerCoords pointerCoords(float x, float y) { + PointerCoords pointerCoords = new PointerCoords(); + pointerCoords.x = x; + pointerCoords.y = y; + return pointerCoords; + } +}