first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AppRegistryBinding.h"
#include <cxxreact/TraceSection.h>
#include <react/renderer/uimanager/primitives.h>
namespace facebook::react {
/* static */ void AppRegistryBinding::startSurface(
jsi::Runtime& runtime,
SurfaceId surfaceId,
const std::string& moduleName,
const folly::dynamic& initialProps,
DisplayMode displayMode) {
TraceSection s("AppRegistryBinding::startSurface");
jsi::Object parameters(runtime);
parameters.setProperty(runtime, "rootTag", surfaceId);
parameters.setProperty(
runtime, "initialProps", jsi::valueFromDynamic(runtime, initialProps));
parameters.setProperty(runtime, "fabric", true);
auto global = runtime.global();
auto registry = global.getProperty(runtime, "RN$AppRegistry");
if (!registry.isObject()) {
throw std::runtime_error(
"AppRegistryBinding::startSurface failed. Global was not installed.");
}
auto method = std::move(registry).asObject(runtime).getPropertyAsFunction(
runtime, "runApplication");
method.call(
runtime,
{jsi::String::createFromUtf8(runtime, moduleName),
std::move(parameters),
jsi::Value(runtime, displayModeToInt(displayMode))});
}
/* static */ void AppRegistryBinding::setSurfaceProps(
jsi::Runtime& runtime,
SurfaceId surfaceId,
const std::string& moduleName,
const folly::dynamic& initialProps,
DisplayMode displayMode) {
TraceSection s("UIManagerBinding::setSurfaceProps");
jsi::Object parameters(runtime);
parameters.setProperty(runtime, "rootTag", surfaceId);
parameters.setProperty(
runtime, "initialProps", jsi::valueFromDynamic(runtime, initialProps));
parameters.setProperty(runtime, "fabric", true);
auto global = runtime.global();
auto registry = global.getProperty(runtime, "RN$AppRegistry");
if (!registry.isObject()) {
throw std::runtime_error(
"AppRegistryBinding::setSurfaceProps failed. Global was not installed.");
}
auto method = std::move(registry).asObject(runtime).getPropertyAsFunction(
runtime, "setSurfaceProps");
method.call(
runtime,
{jsi::String::createFromUtf8(runtime, moduleName),
std::move(parameters),
jsi::Value(runtime, displayModeToInt(displayMode))});
}
/* static */ void AppRegistryBinding::stopSurface(
jsi::Runtime& runtime,
SurfaceId surfaceId) {
auto global = runtime.global();
auto stopFunction = global.getProperty(runtime, "RN$stopSurface");
if (!stopFunction.isObject() ||
!stopFunction.asObject(runtime).isFunction(runtime)) {
throw std::runtime_error(
"AppRegistryBinding::stopSurface failed. Global was not installed.");
}
std::move(stopFunction)
.asObject(runtime)
.asFunction(runtime)
.call(runtime, {jsi::Value{surfaceId}});
}
} // namespace facebook::react

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <folly/dynamic.h>
#include <jsi/jsi.h>
#include <react/renderer/core/ReactPrimitives.h>
namespace facebook::react {
class AppRegistryBinding final {
public:
AppRegistryBinding() = 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,
const std::string &moduleName,
const folly::dynamic &initialProps,
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,
const std::string &moduleName,
const folly::dynamic &initialProps,
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

View File

@@ -0,0 +1,40 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_uimanager_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_uimanager OBJECT ${react_renderer_uimanager_SRC})
target_include_directories(react_renderer_uimanager PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_uimanager
glog
folly_runtime
jsi
react_cxxreact
react_debug
react_featureflags
react_renderer_componentregistry
react_renderer_consistency
react_renderer_uimanager_consistency
react_renderer_bridging
react_renderer_core
react_renderer_debug
react_renderer_dom
react_renderer_graphics
react_renderer_leakchecker
react_renderer_runtimescheduler
react_renderer_mounting
rrc_root
rrc_view
runtimeexecutor
)
target_compile_reactnative_options(react_renderer_uimanager PRIVATE)
target_compile_options(react_renderer_uimanager PRIVATE -Wno-unused-local-typedef)
target_compile_options(react_renderer_uimanager PRIVATE -Wpedantic)

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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::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;
virtual ~LayoutAnimationStatusDelegate() = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,545 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "PointerEventsProcessor.h"
#include <glog/logging.h>
#include <react/renderer/bridging/bridging.h>
namespace facebook::react {
std::shared_ptr<const ShadowNode>
PointerEventsProcessor::getShadowNodeFromEventTarget(
jsi::Runtime& runtime,
const EventTarget* target) {
if (target != nullptr) {
target->retain(runtime);
auto instanceHandle = target->getInstanceHandle(runtime);
target->release(runtime);
if (instanceHandle.isObject()) {
auto handleObj = instanceHandle.asObject(runtime);
if (handleObj.hasProperty(runtime, "stateNode")) {
auto stateNode = handleObj.getProperty(runtime, "stateNode");
if (stateNode.isObject()) {
auto stateNodeObj = stateNode.asObject(runtime);
if (stateNodeObj.hasProperty(runtime, "node")) {
auto node = stateNodeObj.getProperty(runtime, "node");
if (node.isObject()) {
return Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, node);
}
}
}
}
}
}
return nullptr;
}
static bool isViewListeningToEvents(
const ShadowNode& shadowNode,
std::initializer_list<ViewEvents::Offset> eventTypes) {
if (shadowNode.getTraits().check(ShadowNodeTraits::Trait::ViewKind)) {
auto& viewProps = static_cast<const ViewProps&>(*shadowNode.getProps());
for (const ViewEvents::Offset eventType : eventTypes) {
if (viewProps.events[eventType]) {
return true;
}
}
}
return false;
}
static bool isAnyViewInPathToRootListeningToEvents(
const UIManager& uiManager,
const ShadowNode& shadowNode,
std::initializer_list<ViewEvents::Offset> eventTypes) {
// Check the target view first
if (isViewListeningToEvents(shadowNode, eventTypes)) {
return true;
}
// Retrieve the node's root & a list of nodes between the target and the root
auto owningRootShadowNode = std::shared_ptr<const ShadowNode>{};
uiManager.getShadowTreeRegistry().visit(
shadowNode.getSurfaceId(),
[&owningRootShadowNode](const ShadowTree& shadowTree) {
owningRootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
if (owningRootShadowNode == nullptr) {
return false;
}
auto& nodeFamily = shadowNode.getFamily();
auto ancestors = nodeFamily.getAncestors(*owningRootShadowNode);
// Check for listeners from the target's parent to the root
for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) {
auto& currentNode = it->first.get();
if (isViewListeningToEvents(currentNode, eventTypes)) {
return true;
}
}
return false;
}
static PointerEventTarget retargetPointerEvent(
const PointerEvent& event,
const ShadowNode& nodeToTarget,
const UIManager& uiManager) {
PointerEvent retargetedEvent(event);
// TODO: is dereferencing latestNodeToTarget without null checking safe?
auto latestNodeToTarget = uiManager.getNewestCloneOfShadowNode(nodeToTarget);
// Adjust offsetX/Y to be relative to the retargeted node
// HACK: This is a basic/incomplete implementation which simply subtracts
// the retargeted node's origin from the original event's client coordinates.
// More work will be needed to properly take non-trival transforms into
// account.
auto layoutMetrics = uiManager.getRelativeLayoutMetrics(
*latestNodeToTarget,
nullptr,
{/* .includeTransform */ .includeTransform = true});
retargetedEvent.offsetPoint = {
.x = event.clientPoint.x - layoutMetrics.frame.origin.x,
.y = event.clientPoint.y - layoutMetrics.frame.origin.y,
};
PointerEventTarget result = {};
result.event = retargetedEvent;
result.target = latestNodeToTarget;
return result;
}
static std::shared_ptr<const ShadowNode> getCaptureTargetOverride(
PointerIdentifier pointerId,
CaptureTargetOverrideRegistry& registry) {
auto pendingPointerItr = registry.find(pointerId);
if (pendingPointerItr == registry.end()) {
return nullptr;
}
std::weak_ptr<const ShadowNode> maybeTarget = pendingPointerItr->second;
if (maybeTarget.expired()) {
// target has expired so it should functionally behave the same as if it
// was removed from the override list.
registry.erase(pointerId);
return nullptr;
}
return maybeTarget.lock();
}
/*
* Centralized method which determines if an event should be sent to JS by
* inspecing the listeners in the target's view path.
*/
static bool shouldEmitPointerEvent(
const ShadowNode& targetNode,
const std::string& type,
const UIManager& uiManager) {
if (type == "topPointerDown") {
return isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerDown,
ViewEvents::Offset::PointerDownCapture});
} else if (type == "topPointerUp") {
return isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerUp, ViewEvents::Offset::PointerUpCapture});
} else if (type == "topPointerMove") {
return isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerMove,
ViewEvents::Offset::PointerMoveCapture});
} else if (type == "topPointerEnter") {
// This event goes through the capturing phase in full but only bubble
// through the target and no futher up the tree
return isViewListeningToEvents(
targetNode, {ViewEvents::Offset::PointerEnter}) ||
isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerEnterCapture});
} else if (type == "topPointerLeave") {
// This event goes through the capturing phase in full but only bubble
// through the target and no futher up the tree
return isViewListeningToEvents(
targetNode, {ViewEvents::Offset::PointerLeave}) ||
isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerLeaveCapture});
} else if (type == "topPointerOver") {
return isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerOver,
ViewEvents::Offset::PointerOverCapture});
} else if (type == "topPointerOut") {
return isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::PointerOut,
ViewEvents::Offset::PointerOutCapture});
} else if (type == "topClick") {
return isAnyViewInPathToRootListeningToEvents(
uiManager,
targetNode,
{ViewEvents::Offset::Click, ViewEvents::Offset::ClickCapture});
}
// This is more of an optimization method so if we encounter a type which
// has not been specifically addressed above we should just let it through.
return true;
}
void PointerEventsProcessor::interceptPointerEvent(
const std::shared_ptr<const ShadowNode>& target,
const std::string& type,
ReactEventPriority priority,
const PointerEvent& event,
const DispatchEvent& eventDispatcher,
const UIManager& uiManager) {
// Process all pending pointer capture assignments
processPendingPointerCapture(event, eventDispatcher, uiManager);
PointerEvent pointerEvent(event);
std::shared_ptr<const ShadowNode> targetNode = target;
// Retarget the event if it has a pointer capture override target
auto overrideTarget = getCaptureTargetOverride(
pointerEvent.pointerId, pendingPointerCaptureTargetOverrides_);
if (overrideTarget != nullptr &&
overrideTarget->getTag() != targetNode->getTag()) {
auto retargeted =
retargetPointerEvent(pointerEvent, *overrideTarget, uiManager);
pointerEvent = retargeted.event;
targetNode = retargeted.target;
}
if (type == "topClick") {
// Click events are synthetic so should just be passed on instead of going
// through any sort of processing.
eventDispatcher(*targetNode, type, priority, pointerEvent);
return;
}
if (type == "topPointerDown") {
registerActivePointer(pointerEvent);
} else if (type == "topPointerMove") {
// TODO: Remove the need for this check by properly handling
// pointerenter/pointerleave events emitted from the native platform
if (getActivePointer(pointerEvent.pointerId) != nullptr) {
updateActivePointer(pointerEvent);
}
}
// Getting a pointerleave event from the platform is a special case telling us
// that the pointer has left the root so we don't forward the event raw but
// instead just run through our hover tracking logic with a null target.
//
// Notably: we do not forward the platform's leave event but instead will emit
// leave events through our unified hover tracking logic.
if (type == "topPointerLeave") {
handleIncomingPointerEventOnNode(
pointerEvent, nullptr, eventDispatcher, uiManager);
} else {
handleIncomingPointerEventOnNode(
pointerEvent, targetNode, eventDispatcher, uiManager);
if (shouldEmitPointerEvent(*targetNode, type, uiManager)) {
eventDispatcher(*targetNode, type, priority, pointerEvent);
}
// All pointercancel events and certain pointerup events (when using an
// direct pointer w/o the concept of hover) should be treated as the
// pointer leaving the device entirely so we go through our hover tracking
// logic again but pass in a null target.
auto activePointer = getActivePointer(pointerEvent.pointerId);
if (type == "topPointerCancel" ||
(type == "topPointerUp" && activePointer != nullptr &&
activePointer->shouldLeaveWhenReleased)) {
handleIncomingPointerEventOnNode(
pointerEvent, nullptr, eventDispatcher, uiManager);
}
}
// Implicit pointer capture release
if (overrideTarget != nullptr &&
(type == "topPointerUp" || type == "topPointerCancel")) {
releasePointerCapture(pointerEvent.pointerId, overrideTarget.get());
processPendingPointerCapture(pointerEvent, eventDispatcher, uiManager);
}
if (type == "topPointerUp" || type == "topPointerCancel") {
unregisterActivePointer(pointerEvent);
}
}
void PointerEventsProcessor::setPointerCapture(
PointerIdentifier pointerId,
const std::shared_ptr<const ShadowNode>& shadowNode) {
if (auto activePointer = getActivePointer(pointerId)) {
// As per the spec this method should silently fail if the pointer in
// question does not have any active buttons
if (activePointer->event.buttons == 0) {
return;
}
pendingPointerCaptureTargetOverrides_[pointerId] = shadowNode;
} else {
// TODO: Throw DOMException with name "NotFoundError" when pointerId does
// not match any of the active pointers
}
}
void PointerEventsProcessor::releasePointerCapture(
PointerIdentifier pointerId,
const ShadowNode* shadowNode) {
if (getActivePointer(pointerId) != nullptr) {
// We only clear the pointer's capture target override if release was called
// on the shadowNode which has the capture override, otherwise the result
// should no-op
auto pendingTarget = getCaptureTargetOverride(
pointerId, pendingPointerCaptureTargetOverrides_);
if (pendingTarget != nullptr &&
pendingTarget->getTag() == shadowNode->getTag()) {
pendingPointerCaptureTargetOverrides_.erase(pointerId);
}
} else {
// TODO: Throw DOMException with name "NotFoundError" when pointerId does
// not match any of the active pointers
}
}
bool PointerEventsProcessor::hasPointerCapture(
PointerIdentifier pointerId,
const ShadowNode* shadowNode) {
std::shared_ptr<const ShadowNode> pendingTarget = getCaptureTargetOverride(
pointerId, pendingPointerCaptureTargetOverrides_);
if (pendingTarget != nullptr) {
return pendingTarget->getTag() == shadowNode->getTag();
}
return false;
}
ActivePointer* PointerEventsProcessor::getActivePointer(
PointerIdentifier pointerId) {
auto it = activePointers_.find(pointerId);
return (it == activePointers_.end()) ? nullptr : &it->second;
}
void PointerEventsProcessor::registerActivePointer(const PointerEvent& event) {
ActivePointer activePointer = {};
activePointer.event = event;
// If the pointer has not been tracked by the hover infrastructure then when
// the pointer is released we're gonna have to treat it as if the pointer is
// leaving the screen entirely.
activePointer.shouldLeaveWhenReleased =
previousHoverTrackersPerPointer_.find(event.pointerId) ==
previousHoverTrackersPerPointer_.end();
activePointers_[event.pointerId] = activePointer;
}
void PointerEventsProcessor::updateActivePointer(const PointerEvent& event) {
if (auto activePointer = getActivePointer(event.pointerId)) {
activePointer->event = event;
} else {
LOG(WARNING)
<< "Inconsistency between local and platform pointer registries: attempting to update an active pointer which has never been registered.";
}
}
void PointerEventsProcessor::unregisterActivePointer(
const PointerEvent& event) {
if (getActivePointer(event.pointerId) != nullptr) {
activePointers_.erase(event.pointerId);
} else {
LOG(WARNING)
<< "Inconsistency between local and platform pointer registries: attempting to unregister an active pointer which has never been registered.";
}
}
void PointerEventsProcessor::processPendingPointerCapture(
const PointerEvent& event,
const DispatchEvent& eventDispatcher,
const UIManager& uiManager) {
auto pendingOverride = getCaptureTargetOverride(
event.pointerId, pendingPointerCaptureTargetOverrides_);
bool hasPendingOverride = pendingOverride != nullptr;
auto activeOverride = getCaptureTargetOverride(
event.pointerId, activePointerCaptureTargetOverrides_);
bool hasActiveOverride = activeOverride != nullptr;
if (!hasPendingOverride && !hasActiveOverride) {
return;
}
auto pendingOverrideTag =
(hasPendingOverride) ? pendingOverride->getTag() : -1;
auto activeOverrideTag = (hasActiveOverride) ? activeOverride->getTag() : -1;
if (hasActiveOverride && activeOverrideTag != pendingOverrideTag) {
auto retargeted = retargetPointerEvent(event, *activeOverride, uiManager);
if (shouldEmitPointerEvent(
*retargeted.target, "topLostPointerCapture", uiManager)) {
eventDispatcher(
*retargeted.target,
"topLostPointerCapture",
ReactEventPriority::Discrete,
retargeted.event);
}
}
if (hasPendingOverride && activeOverrideTag != pendingOverrideTag) {
auto retargeted = retargetPointerEvent(event, *pendingOverride, uiManager);
if (shouldEmitPointerEvent(
*retargeted.target, "topGotPointerCapture", uiManager)) {
eventDispatcher(
*retargeted.target,
"topGotPointerCapture",
ReactEventPriority::Discrete,
retargeted.event);
}
}
if (!hasPendingOverride) {
activePointerCaptureTargetOverrides_.erase(event.pointerId);
} else {
activePointerCaptureTargetOverrides_[event.pointerId] = pendingOverride;
}
}
void PointerEventsProcessor::handleIncomingPointerEventOnNode(
const PointerEvent& event,
const std::shared_ptr<const ShadowNode>& targetNode,
const DispatchEvent& eventDispatcher,
const UIManager& uiManager) {
// Get the hover tracker from the previous event (default to null if the
// pointer hasn't been tracked before)
auto prevHoverTrackerIt =
previousHoverTrackersPerPointer_.find(event.pointerId);
PointerHoverTracker::Unique prevHoverTracker =
prevHoverTrackerIt != previousHoverTrackersPerPointer_.end()
? std::move(prevHoverTrackerIt->second)
: std::make_unique<PointerHoverTracker>(nullptr, uiManager);
// The previous tracker was stored from a previous tick so we mark it as old
prevHoverTracker->markAsOld();
auto curHoverTracker =
std::make_unique<PointerHoverTracker>(targetNode, uiManager);
// Out
if (!prevHoverTracker->hasSameTarget(*curHoverTracker) &&
prevHoverTracker->areAnyTargetsListeningToEvents(
{ViewEvents::Offset::PointerOut,
ViewEvents::Offset::PointerOutCapture},
uiManager)) {
auto prevTarget = prevHoverTracker->getTarget(uiManager);
if (prevTarget != nullptr) {
eventDispatcher(
*prevTarget, "topPointerOut", ReactEventPriority::Discrete, event);
}
}
// REMINDER: The order of these lists are from the root to the target
const auto [leavingNodes, enteringNodes] =
prevHoverTracker->diffEventPath(*curHoverTracker, uiManager);
// Leaving
// pointerleave events need to be emitted from the deepest target to the root
// but we also need to efficiently keep track of if a view has a parent which
// is listening to the leave events, so we first iterate from the root to the
// target, collecting the views which need events fired for, of which we
// reverse iterate (now from target to root), actually emitting the events.
bool hasParentLeaveCaptureListener = false;
std::vector<std::reference_wrapper<const ShadowNode>> targetsToEmitLeaveTo;
for (auto nodeRef : leavingNodes) {
const auto& node = nodeRef.get();
bool hasCapturingListener = isViewListeningToEvents(
node, {ViewEvents::Offset::PointerLeaveCapture});
bool shouldEmitEvent = hasParentLeaveCaptureListener ||
hasCapturingListener ||
isViewListeningToEvents(node, {ViewEvents::Offset::PointerLeave});
if (shouldEmitEvent) {
targetsToEmitLeaveTo.emplace_back(node);
}
if (hasCapturingListener && !hasParentLeaveCaptureListener) {
hasParentLeaveCaptureListener = true;
}
}
// Actually emit the leave events (in order from target to root)
for (auto it = targetsToEmitLeaveTo.rbegin();
it != targetsToEmitLeaveTo.rend();
it++) {
eventDispatcher(
*it, "topPointerLeave", ReactEventPriority::Discrete, event);
}
// Over
if (!prevHoverTracker->hasSameTarget(*curHoverTracker) &&
curHoverTracker->areAnyTargetsListeningToEvents(
{ViewEvents::Offset::PointerOver,
ViewEvents::Offset::PointerOverCapture},
uiManager)) {
auto curTarget = curHoverTracker->getTarget(uiManager);
if (curTarget != nullptr) {
eventDispatcher(
*curTarget, "topPointerOver", ReactEventPriority::Discrete, event);
}
}
// Entering
// We want to impose the same filtering based on what events are being
// listened to as we did with leaving earlier in this function but we can emit
// the events in this loop inline since it's expected to fire the evens in
// order from root to target.
bool hasParentEnterCaptureListener = false;
for (auto nodeRef : enteringNodes) {
const auto& node = nodeRef.get();
bool hasCapturingListener = isViewListeningToEvents(
node, {ViewEvents::Offset::PointerEnterCapture});
bool shouldEmitEvent = hasParentEnterCaptureListener ||
hasCapturingListener ||
isViewListeningToEvents(node, {ViewEvents::Offset::PointerEnter});
if (shouldEmitEvent) {
eventDispatcher(
node, "topPointerEnter", ReactEventPriority::Discrete, event);
}
if (hasCapturingListener && !hasParentEnterCaptureListener) {
hasParentEnterCaptureListener = true;
}
}
if (targetNode != nullptr) {
previousHoverTrackersPerPointer_[event.pointerId] =
std::move(curHoverTracker);
} else {
previousHoverTrackersPerPointer_.erase(event.pointerId);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <functional>
#include <jsi/jsi.h>
#include <react/renderer/uimanager/PointerHoverTracker.h>
#include <react/renderer/uimanager/UIManager.h>
namespace facebook::react {
// Helper struct to package a PointerEvent and SharedEventTarget together
struct PointerEventTarget {
PointerEvent event;
std::shared_ptr<const ShadowNode> target;
};
// Helper struct to contain an active pointer's event data along with additional
// metadata
struct ActivePointer {
PointerEvent event;
/*
* Informs the event system that when the touch is released it should be
* treated as the pointer leaving the screen entirely.
*/
bool shouldLeaveWhenReleased{};
};
using DispatchEvent = std::function<void(
const ShadowNode &targetNode,
const std::string &type,
ReactEventPriority priority,
const EventPayload &payload)>;
using PointerIdentifier = int32_t;
using CaptureTargetOverrideRegistry = std::unordered_map<PointerIdentifier, std::weak_ptr<const ShadowNode>>;
using ActivePointerRegistry = std::unordered_map<PointerIdentifier, ActivePointer>;
using PointerHoverTrackerRegistry = std::unordered_map<PointerIdentifier, PointerHoverTracker::Unique>;
class PointerEventsProcessor final {
public:
static std::shared_ptr<const ShadowNode> getShadowNodeFromEventTarget(
jsi::Runtime &runtime,
const EventTarget *target);
void interceptPointerEvent(
const std::shared_ptr<const ShadowNode> &target,
const std::string &type,
ReactEventPriority priority,
const PointerEvent &event,
const DispatchEvent &eventDispatcher,
const UIManager &uiManager);
void setPointerCapture(PointerIdentifier pointerId, const std::shared_ptr<const ShadowNode> &shadowNode);
void releasePointerCapture(PointerIdentifier pointerId, const ShadowNode *shadowNode);
bool hasPointerCapture(PointerIdentifier pointerId, const ShadowNode *shadowNode);
private:
ActivePointer *getActivePointer(PointerIdentifier pointerId);
void registerActivePointer(const PointerEvent &event);
void updateActivePointer(const PointerEvent &event);
void unregisterActivePointer(const PointerEvent &event);
void processPendingPointerCapture(
const PointerEvent &event,
const DispatchEvent &eventDispatcher,
const UIManager &uiManager);
ActivePointerRegistry activePointers_;
CaptureTargetOverrideRegistry pendingPointerCaptureTargetOverrides_;
CaptureTargetOverrideRegistry activePointerCaptureTargetOverrides_;
/*
* Private method which is used for tracking the location of pointer events to
* manage the entering/leaving events. The primary idea is that a pointer's
* presence & movement is dicated by a variety of underlying events such as
* down, move, and up — and they should all be treated the same when it comes
* to tracking the entering & leaving of pointers to views. This method
* accomplishes that by receiving the pointer event, and the target view (can
* be null in cases when the event indicates that the pointer has left the
* screen entirely)
*/
void handleIncomingPointerEventOnNode(
const PointerEvent &event,
const std::shared_ptr<const ShadowNode> &targetNode,
const DispatchEvent &eventDispatcher,
const UIManager &uiManager);
PointerHoverTrackerRegistry previousHoverTrackersPerPointer_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "PointerHoverTracker.h"
#include <utility>
namespace facebook::react {
using EventPath = PointerHoverTracker::EventPath;
PointerHoverTracker::PointerHoverTracker(
std::shared_ptr<const ShadowNode> target,
const UIManager& uiManager)
: target_(std::move(target)) {
if (target_ != nullptr) {
// Retrieve the root shadow node at this current revision so that we can
// leverage it to get the event path list at the moment the event occured
auto rootShadowNode = std::shared_ptr<const ShadowNode>{};
auto& shadowTreeRegistry = uiManager.getShadowTreeRegistry();
shadowTreeRegistry.visit(
target_->getSurfaceId(),
[&rootShadowNode](const ShadowTree& shadowTree) {
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
this->root_ = rootShadowNode;
}
}
bool PointerHoverTracker::hasSameTarget(
const PointerHoverTracker& other) const {
if (target_ != nullptr && other.target_ != nullptr) {
return ShadowNode::sameFamily(*this->target_, *other.target_);
}
return false;
}
bool PointerHoverTracker::areAnyTargetsListeningToEvents(
std::initializer_list<ViewEvents::Offset> eventTypes,
const UIManager& uiManager) const {
auto eventPath = getEventPathTargets();
for (const auto& oldTarget : eventPath) {
auto newestTarget = uiManager.getNewestCloneOfShadowNode(oldTarget);
if (newestTarget &&
newestTarget->getTraits().check(ShadowNodeTraits::Trait::ViewKind)) {
auto eventFlags =
static_cast<const ViewProps&>(*newestTarget->getProps()).events;
for (const auto& eventType : eventTypes) {
if (eventFlags[eventType]) {
return true;
}
}
}
}
return false;
}
std::tuple<EventPath, EventPath> PointerHoverTracker::diffEventPath(
const PointerHoverTracker& other,
const UIManager& uiManager) const {
auto myEventPath = getEventPathTargets();
auto otherEventPath = other.getEventPathTargets();
// Starting from the root node, iterate through both event paths, comparing
// the nodes' families until a difference is found, and then just break out of
// the loop early. This will leave the iterators for each path at the point
// where the event paths diverge and can be subsequently used as the beginning
// iterator of a subrange, where the subrange on *this* tracker would
// represent the removed views, and the subrange on *other* tracker would
// represent the added views.
//
// NOTE: This works based on the assumption that nodes in react-native don't
// get "re-parented" so if there are any bugs reported due to extra
// leave->enter events, this solution may need to be revisited with a more
// robust diffing solution.
auto myIt = myEventPath.rbegin();
auto otherIt = otherEventPath.rbegin();
while (myIt != myEventPath.rend() && otherIt != otherEventPath.rend()) {
if (!ShadowNode::sameFamily(myIt->get(), otherIt->get())) {
break;
}
++myIt;
++otherIt;
}
EventPath removed;
for (auto nodeIt = myIt; nodeIt != myEventPath.rend(); nodeIt++) {
const auto& latestNode = getLatestNode(*nodeIt, uiManager);
if (latestNode != nullptr) {
removed.emplace_back(*latestNode);
}
}
EventPath added;
for (auto nodeIt = otherIt; nodeIt != otherEventPath.rend(); nodeIt++) {
const auto& latestNode = other.getLatestNode(*nodeIt, uiManager);
if (latestNode != nullptr) {
added.emplace_back(*latestNode);
}
}
return std::make_tuple(removed, added);
}
const ShadowNode* PointerHoverTracker::getTarget(
const UIManager& uiManager) const {
if (target_ == nullptr) {
return nullptr;
}
return getLatestNode(*target_, uiManager);
}
void PointerHoverTracker::markAsOld() {
isOldTracker_ = true;
}
const ShadowNode* PointerHoverTracker::getLatestNode(
const ShadowNode& node,
const UIManager& uiManager) const {
if (isOldTracker_) {
auto newestTarget = uiManager.getNewestCloneOfShadowNode(node);
return newestTarget.get();
}
return &node;
}
EventPath PointerHoverTracker::getEventPathTargets() const {
EventPath result{};
if (target_ == nullptr || root_ == nullptr) {
return result;
}
auto ancestors = target_->getFamily().getAncestors(*root_);
result.emplace_back(*target_);
for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) {
result.push_back(it->first);
}
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <functional>
#include <initializer_list>
#include <memory>
#include <react/renderer/components/view/primitives.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/uimanager/UIManager.h>
namespace facebook::react {
class PointerHoverTracker {
public:
using Unique = std::unique_ptr<PointerHoverTracker>;
using EventPath = std::vector<std::reference_wrapper<const ShadowNode>>;
PointerHoverTracker(std::shared_ptr<const ShadowNode> target, const UIManager &uiManager);
const ShadowNode *getTarget(const UIManager &uiManager) const;
bool hasSameTarget(const PointerHoverTracker &other) const;
bool areAnyTargetsListeningToEvents(std::initializer_list<ViewEvents::Offset> eventTypes, const UIManager &uiManager)
const;
/**
* Performs a diff between the current and given trackers and returns a tuple
* containing [1]: the nodes that have been removed and [2]: the nodes that
* have been added. Note that the order of these lists are from parents ->
* children.
*/
std::tuple<EventPath, EventPath> diffEventPath(const PointerHoverTracker &other, const UIManager &uiManager) const;
void markAsOld();
private:
/**
* Flag that lets the tracker know if it needs to fetch the latest version of
* the shadow node or not.
*/
bool isOldTracker_ = false;
std::shared_ptr<const ShadowNode> root_;
std::shared_ptr<const ShadowNode> target_;
/**
* A thin wrapper around `UIManager::getNewestCloneOfShadowNode` that only
* actually gets the newest clone only if the tracker is marked as "old".
*/
const ShadowNode *getLatestNode(const ShadowNode &node, const UIManager &uiManager) const;
/**
* Retrieves the list of shadow node references in the event's path starting
* from the target node to the root node.
*/
EventPath getEventPathTargets() const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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
#warning "The SurfaceRegistryBinding.h header has been renamed to AppRegistryBinding.h"
#include <react/renderer/uimanager/AppRegistryBinding.h>

View File

@@ -0,0 +1,737 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "UIManager.h"
#include <cxxreact/JSExecutor.h>
#include <cxxreact/TraceSection.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/core/DynamicPropsUtilities.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/ShadowNodeFragment.h>
#include <react/renderer/uimanager/AppRegistryBinding.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <react/renderer/uimanager/UIManagerCommitHook.h>
#include <react/renderer/uimanager/UIManagerMountHook.h>
#include <glog/logging.h>
#include <utility>
namespace {
std::unique_ptr<facebook::react::LeakChecker> constructLeakCheckerIfNeeded(
const facebook::react::RuntimeExecutor& runtimeExecutor) {
#ifdef REACT_NATIVE_DEBUG
return std::make_unique<facebook::react::LeakChecker>(runtimeExecutor);
#else
return {};
#endif
}
} // namespace
namespace facebook::react {
// Explicitly define destructors here, as they have to exist in order to act as
// a "key function" for the ShadowNodeWrapper class -- this allows for RTTI to
// work properly across dynamic library boundaries (i.e. dynamic_cast that is
// used by getNativeState method)
ShadowNodeListWrapper::~ShadowNodeListWrapper() = default;
UIManager::UIManager(
const RuntimeExecutor& runtimeExecutor,
std::shared_ptr<const ContextContainer> contextContainer)
: runtimeExecutor_(runtimeExecutor),
shadowTreeRegistry_(),
contextContainer_(std::move(contextContainer)),
leakChecker_(constructLeakCheckerIfNeeded(runtimeExecutor)),
lazyShadowTreeRevisionConsistencyManager_(
std::make_unique<LazyShadowTreeRevisionConsistencyManager>(
shadowTreeRegistry_)) {}
UIManager::~UIManager() {
LOG(WARNING) << "UIManager::~UIManager() was called (address: " << this
<< ").";
}
std::shared_ptr<ShadowNode> UIManager::createNode(
Tag tag,
const std::string& name,
SurfaceId surfaceId,
RawProps rawProps,
InstanceHandle::Shared instanceHandle) const {
TraceSection s("UIManager::createNode", "componentName", name);
auto& componentDescriptor = componentDescriptorRegistry_->at(name);
auto fallbackDescriptor =
componentDescriptorRegistry_->getFallbackComponentDescriptor();
PropsParserContext propsParserContext{surfaceId, *contextContainer_};
auto family = componentDescriptor.createFamily(
{.tag = tag,
.surfaceId = surfaceId,
.instanceHandle = std::move(instanceHandle)});
const auto props = componentDescriptor.cloneProps(
propsParserContext, nullptr, std::move(rawProps));
const auto state = componentDescriptor.createInitialState(props, family);
auto shadowNode = componentDescriptor.createShadowNode(
ShadowNodeFragment{
.props = fallbackDescriptor != nullptr &&
fallbackDescriptor->getComponentHandle() ==
componentDescriptor.getComponentHandle()
? componentDescriptor.cloneProps(
propsParserContext,
props,
RawProps(folly::dynamic::object("name", name)))
: props,
.children = ShadowNodeFragment::childrenPlaceholder(),
.state = state,
},
family);
if (delegate_ != nullptr) {
delegate_->uiManagerDidCreateShadowNode(*shadowNode);
}
if (leakChecker_) {
leakChecker_->uiManagerDidCreateShadowNodeFamily(family);
}
return shadowNode;
}
std::shared_ptr<ShadowNode> UIManager::cloneNode(
const ShadowNode& shadowNode,
const ShadowNode::SharedListOfShared& children,
RawProps rawProps) const {
TraceSection s(
"UIManager::cloneNode", "componentName", shadowNode.getComponentName());
PropsParserContext propsParserContext{
shadowNode.getFamily().getSurfaceId(), *contextContainer_};
auto& componentDescriptor = shadowNode.getComponentDescriptor();
auto& family = shadowNode.getFamily();
auto props = ShadowNodeFragment::propsPlaceholder();
if (!rawProps.isEmpty()) {
if (family.nativeProps_DEPRECATED != nullptr) {
// 1. update the nativeProps_DEPRECATED props.
//
// In this step, we want the most recent value for the props
// managed by setNativeProps.
// Values in `rawProps` patch (take precedence over)
// `nativeProps_DEPRECATED`. For example, if both
// `nativeProps_DEPRECATED` and `rawProps` contain key 'A'.
// Value from `rawProps` overrides what was previously in
// `nativeProps_DEPRECATED`. Notice that the `nativeProps_DEPRECATED`
// patch will not get more props from `rawProps`: if the key is not
// present in `nativeProps_DEPRECATED`, it will not be added.
//
// The result of this operation is the new `nativeProps_DEPRECATED`.
family.nativeProps_DEPRECATED =
std::make_unique<folly::dynamic>(mergeDynamicProps(
*family.nativeProps_DEPRECATED, // source
(folly::dynamic)rawProps, // patch
NullValueStrategy::Ignore));
// 2. Compute the final set of props.
//
// This step takes the new props handled by `setNativeProps` and
// merges them in the `rawProps` managed by React.
// The new props handled by `nativeProps` now takes precedence
// on the props handled by React, as we want to make sure that
// all the props are applied to the component.
// We use these finalProps as source of truth for the component.
auto finalProps = mergeDynamicProps(
(folly::dynamic)rawProps, // source
*family.nativeProps_DEPRECATED, // patch
NullValueStrategy::Override);
// 3. Clone the props by using finalProps.
props = componentDescriptor.cloneProps(
propsParserContext, shadowNode.getProps(), RawProps(finalProps));
} else {
props = componentDescriptor.cloneProps(
propsParserContext, shadowNode.getProps(), std::move(rawProps));
}
}
auto clonedShadowNode = componentDescriptor.cloneShadowNode(
shadowNode,
{
.props = props,
.children = children,
.runtimeShadowNodeReference = false,
});
return clonedShadowNode;
}
void UIManager::appendChild(
const std::shared_ptr<const ShadowNode>& parentShadowNode,
const std::shared_ptr<const ShadowNode>& childShadowNode) const {
TraceSection s("UIManager::appendChild");
auto& componentDescriptor = parentShadowNode->getComponentDescriptor();
componentDescriptor.appendChild(parentShadowNode, childShadowNode);
}
void UIManager::completeSurface(
SurfaceId surfaceId,
const ShadowNode::UnsharedListOfShared& rootChildren,
ShadowTree::CommitOptions commitOptions) {
TraceSection s("UIManager::completeSurface", "surfaceId", surfaceId);
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
auto result = shadowTree.commit(
[&](const RootShadowNode& oldRootShadowNode) {
return std::make_shared<RootShadowNode>(
oldRootShadowNode,
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = rootChildren,
});
},
commitOptions);
if (result == ShadowTree::CommitStatus::Succeeded) {
// It's safe to update the visible revision of the shadow tree immediately
// after we commit a specific one.
lazyShadowTreeRevisionConsistencyManager_->updateCurrentRevision(
surfaceId, shadowTree.getCurrentRevision().rootShadowNode);
}
});
}
void UIManager::setIsJSResponder(
const std::shared_ptr<const ShadowNode>& shadowNode,
bool isJSResponder,
bool blockNativeResponder) const {
if (delegate_ != nullptr) {
delegate_->uiManagerDidSetIsJSResponder(
shadowNode, isJSResponder, blockNativeResponder);
}
}
void UIManager::startSurface(
ShadowTree::Unique&& shadowTree,
const std::string& moduleName,
const folly::dynamic& props,
DisplayMode displayMode) const noexcept {
TraceSection s("UIManager::startSurface");
auto surfaceId = shadowTree->getSurfaceId();
shadowTreeRegistry_.add(std::move(shadowTree));
shadowTreeRegistry_.visit(
surfaceId, [delegate = delegate_](const ShadowTree& shadowTree) {
if (delegate != nullptr) {
delegate->uiManagerDidStartSurface(shadowTree);
}
});
runtimeExecutor_([=](jsi::Runtime& runtime) {
TraceSection s("UIManager::startSurface::onRuntime");
AppRegistryBinding::startSurface(
runtime, surfaceId, moduleName, props, displayMode);
});
}
void UIManager::startEmptySurface(
ShadowTree::Unique&& shadowTree) const noexcept {
TraceSection s("UIManager::startEmptySurface");
shadowTreeRegistry_.add(std::move(shadowTree));
}
void UIManager::setSurfaceProps(
SurfaceId surfaceId,
const std::string& moduleName,
const folly::dynamic& props,
DisplayMode displayMode) const noexcept {
TraceSection s("UIManager::setSurfaceProps");
runtimeExecutor_([=](jsi::Runtime& runtime) {
AppRegistryBinding::setSurfaceProps(
runtime, surfaceId, moduleName, props, displayMode);
});
}
ShadowTree::Unique UIManager::stopSurface(SurfaceId surfaceId) const {
TraceSection s("UIManager::stopSurface");
// Stop any ongoing animations.
stopSurfaceForAnimationDelegate(surfaceId);
// Waiting for all concurrent commits to be finished and unregistering the
// `ShadowTree`.
auto shadowTree = getShadowTreeRegistry().remove(surfaceId);
if (shadowTree) {
// We execute JavaScript/React part of the process at the very end to
// minimize any visible side-effects of stopping the Surface. Any possible
// commits from the JavaScript side will not be able to reference a
// `ShadowTree` and will fail silently.
runtimeExecutor_([=](jsi::Runtime& runtime) {
AppRegistryBinding::stopSurface(runtime, surfaceId);
});
if (leakChecker_) {
leakChecker_->stopSurface(surfaceId);
}
}
return shadowTree;
}
std::shared_ptr<const ShadowNode> UIManager::getNewestCloneOfShadowNode(
const ShadowNode& shadowNode) const {
auto ancestorShadowNode = std::shared_ptr<const ShadowNode>{};
shadowTreeRegistry_.visit(
shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) {
ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
return getShadowNodeInSubtree(shadowNode, ancestorShadowNode);
}
std::shared_ptr<const ShadowNode> UIManager::getShadowNodeInSubtree(
const ShadowNode& shadowNode,
const std::shared_ptr<const ShadowNode>& ancestorShadowNode) const {
if (!ancestorShadowNode) {
return nullptr;
}
// If the given shadow node is of the same family as the root shadow node,
// return the latest root shadow node
if (ShadowNode::sameFamily(*ancestorShadowNode, shadowNode)) {
return ancestorShadowNode;
}
auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode);
if (ancestors.empty()) {
return nullptr;
}
auto pair = ancestors.rbegin();
return pair->first.get().getChildren().at(pair->second);
}
ShadowTreeRevisionConsistencyManager*
UIManager::getShadowTreeRevisionConsistencyManager() {
return lazyShadowTreeRevisionConsistencyManager_.get();
}
ShadowTreeRevisionProvider* UIManager::getShadowTreeRevisionProvider() {
return lazyShadowTreeRevisionConsistencyManager_.get();
}
std::shared_ptr<const ShadowNode> UIManager::findNodeAtPoint(
const std::shared_ptr<const ShadowNode>& node,
Point point) const {
return LayoutableShadowNode::findNodeAtPoint(
getNewestCloneOfShadowNode(*node), point);
}
LayoutMetrics UIManager::getRelativeLayoutMetrics(
const ShadowNode& shadowNode,
const ShadowNode* ancestorShadowNode,
LayoutableShadowNode::LayoutInspectingPolicy policy) const {
TraceSection 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 = std::shared_ptr<const ShadowNode>{};
if (ancestorShadowNode == nullptr) {
shadowTreeRegistry_.visit(
shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) {
owningAncestorShadowNode =
shadowTree.getCurrentRevision().rootShadowNode;
ancestorShadowNode = owningAncestorShadowNode.get();
});
} else {
// 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();
}
auto layoutableAncestorShadowNode =
dynamic_cast<const LayoutableShadowNode*>(ancestorShadowNode);
if (layoutableAncestorShadowNode == nullptr) {
return EmptyLayoutMetrics;
}
return LayoutableShadowNode::computeRelativeLayoutMetrics(
shadowNode.getFamily(), *layoutableAncestorShadowNode, policy);
}
void UIManager::updateState(const StateUpdate& stateUpdate) const {
TraceSection s(
"UIManager::updateState",
"componentName",
stateUpdate.family->getComponentName());
auto& callback = stateUpdate.callback;
auto& family = stateUpdate.family;
auto& componentDescriptor = family->getComponentDescriptor();
shadowTreeRegistry_.visit(
family->getSurfaceId(), [&](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& oldRootShadowNode) {
auto isValid = true;
auto rootNode = oldRootShadowNode.cloneTree(
*family, [&](const ShadowNode& oldShadowNode) {
auto newData =
callback(oldShadowNode.getState()->getDataPointer());
if (!newData) {
isValid = false;
// Just return something, we will discard it anyway.
return oldShadowNode.clone({});
}
auto newState =
componentDescriptor.createState(*family, newData);
return oldShadowNode.clone(
{.props = ShadowNodeFragment::propsPlaceholder(),
.children = ShadowNodeFragment::childrenPlaceholder(),
.state = newState});
});
return isValid
? std::static_pointer_cast<RootShadowNode>(rootNode)
: nullptr;
},
{/* default commit options */});
});
}
void UIManager::dispatchCommand(
const std::shared_ptr<const ShadowNode>& shadowNode,
const std::string& commandName,
const folly::dynamic& args) const {
if (delegate_ != nullptr) {
delegate_->uiManagerDidDispatchCommand(shadowNode, commandName, args);
}
}
void UIManager::setNativeProps_DEPRECATED(
const std::shared_ptr<const ShadowNode>& shadowNode,
RawProps rawProps) const {
auto& family = shadowNode->getFamily();
if (family.nativeProps_DEPRECATED) {
// Values in `rawProps` patch (take precedence over)
// `nativeProps_DEPRECATED`. For example, if both `nativeProps_DEPRECATED`
// and `rawProps` contain key 'A'. Value from `rawProps` overrides what was
// previously in `nativeProps_DEPRECATED`.
family.nativeProps_DEPRECATED =
std::make_unique<folly::dynamic>(mergeDynamicProps(
*family.nativeProps_DEPRECATED,
(folly::dynamic)rawProps,
NullValueStrategy::Override));
} else {
family.nativeProps_DEPRECATED =
std::make_unique<folly::dynamic>((folly::dynamic)rawProps);
}
shadowTreeRegistry_.visit(
family.getSurfaceId(), [&](const ShadowTree& shadowTree) {
// The lambda passed to `commit` may be executed multiple times.
// We need to create fresh copy of the `RawProps` object each time.
auto ancestorShadowNode =
shadowTree.getCurrentRevision().rootShadowNode;
shadowTree.commit(
[&](const RootShadowNode& oldRootShadowNode) {
auto rootNode = oldRootShadowNode.cloneTree(
family, [&](const ShadowNode& oldShadowNode) {
auto& componentDescriptor =
componentDescriptorRegistry_->at(
shadowNode->getComponentHandle());
PropsParserContext propsParserContext{
family.getSurfaceId(), *contextContainer_};
auto props = componentDescriptor.cloneProps(
propsParserContext,
getShadowNodeInSubtree(*shadowNode, ancestorShadowNode)
->getProps(),
RawProps(rawProps));
return oldShadowNode.clone({/* .props = */ .props = props});
});
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{/* default commit options */});
});
}
void UIManager::sendAccessibilityEvent(
const std::shared_ptr<const ShadowNode>& shadowNode,
const std::string& eventType) {
if (delegate_ != nullptr) {
delegate_->uiManagerDidSendAccessibilityEvent(shadowNode, eventType);
}
}
void UIManager::configureNextLayoutAnimation(
jsi::Runtime& runtime,
const RawValue& config,
const jsi::Value& successCallback,
const jsi::Value& failureCallback) const {
if (animationDelegate_ != nullptr) {
animationDelegate_->uiManagerDidConfigureNextLayoutAnimation(
runtime,
config,
std::move(successCallback),
std::move(failureCallback));
}
}
static std::shared_ptr<const ShadowNode> findShadowNodeByTagRecursively(
std::shared_ptr<const ShadowNode> parentShadowNode,
Tag tag) {
if (parentShadowNode->getTag() == tag) {
return parentShadowNode;
}
for (const std::shared_ptr<const ShadowNode>& shadowNode :
parentShadowNode->getChildren()) {
auto result = findShadowNodeByTagRecursively(shadowNode, tag);
if (result) {
return result;
}
}
return nullptr;
}
std::shared_ptr<const ShadowNode> UIManager::findShadowNodeByTag_DEPRECATED(
Tag tag) const {
auto shadowNode = std::shared_ptr<const ShadowNode>{};
shadowTreeRegistry_.enumerate([&](const ShadowTree& shadowTree, bool& stop) {
const RootShadowNode* rootShadowNode = nullptr;
// The public interface of `ShadowTree` discourages accessing a stored
// pointer to a root node because of the possible data race.
// To work around this, we ask for a commit and immediately cancel it
// returning `nullptr` instead of a new shadow tree.
// We don't want to add a way to access a stored pointer to a root node
// because this `findShadowNodeByTag` is deprecated. It is only added
// to make migration to the new architecture easier.
shadowTree.tryCommit(
[&](const RootShadowNode& oldRootShadowNode) {
rootShadowNode = &oldRootShadowNode;
return nullptr;
},
{/* default commit options */});
if (rootShadowNode != nullptr) {
const auto& children = rootShadowNode->getChildren();
if (!children.empty()) {
const auto& child = children.front();
shadowNode = findShadowNodeByTagRecursively(child, tag);
if (shadowNode) {
stop = true;
}
}
}
});
return shadowNode;
}
void UIManager::setComponentDescriptorRegistry(
const SharedComponentDescriptorRegistry& componentDescriptorRegistry) {
componentDescriptorRegistry_ = componentDescriptorRegistry;
}
void UIManager::setDelegate(UIManagerDelegate* delegate) {
delegate_ = delegate;
}
UIManagerDelegate* UIManager::getDelegate() {
return delegate_;
}
void UIManager::visitBinding(
const std::function<void(const UIManagerBinding& uiManagerBinding)>&
callback,
jsi::Runtime& runtime) const {
auto uiManagerBinding = UIManagerBinding::getBinding(runtime);
if (uiManagerBinding) {
callback(*uiManagerBinding);
}
}
const ShadowTreeRegistry& UIManager::getShadowTreeRegistry() const {
return shadowTreeRegistry_;
}
void UIManager::registerCommitHook(UIManagerCommitHook& commitHook) {
std::unique_lock lock(commitHookMutex_);
react_native_assert(
std::find(commitHooks_.begin(), commitHooks_.end(), &commitHook) ==
commitHooks_.end());
commitHook.commitHookWasRegistered(*this);
commitHooks_.push_back(&commitHook);
}
void UIManager::unregisterCommitHook(UIManagerCommitHook& commitHook) {
std::unique_lock lock(commitHookMutex_);
auto iterator =
std::find(commitHooks_.begin(), commitHooks_.end(), &commitHook);
react_native_assert(iterator != commitHooks_.end());
commitHooks_.erase(iterator);
commitHook.commitHookWasUnregistered(*this);
}
void UIManager::registerMountHook(UIManagerMountHook& mountHook) {
std::unique_lock lock(mountHookMutex_);
react_native_assert(
std::find(mountHooks_.begin(), mountHooks_.end(), &mountHook) ==
mountHooks_.end());
mountHooks_.push_back(&mountHook);
}
void UIManager::unregisterMountHook(UIManagerMountHook& mountHook) {
std::unique_lock lock(mountHookMutex_);
auto iterator = std::find(mountHooks_.begin(), mountHooks_.end(), &mountHook);
react_native_assert(iterator != mountHooks_.end());
mountHooks_.erase(iterator);
}
#pragma mark - ShadowTreeDelegate
RootShadowNode::Unshared UIManager::shadowTreeWillCommit(
const ShadowTree& shadowTree,
const RootShadowNode::Shared& oldRootShadowNode,
const RootShadowNode::Unshared& newRootShadowNode,
const ShadowTree::CommitOptions& commitOptions) const {
TraceSection s("UIManager::shadowTreeWillCommit");
std::shared_lock lock(commitHookMutex_);
auto resultRootShadowNode = newRootShadowNode;
for (auto* commitHook : commitHooks_) {
resultRootShadowNode = commitHook->shadowTreeWillCommit(
shadowTree, oldRootShadowNode, resultRootShadowNode, commitOptions);
}
return resultRootShadowNode;
}
void UIManager::shadowTreeDidFinishTransaction(
std::shared_ptr<const MountingCoordinator> mountingCoordinator,
bool mountSynchronously) const {
TraceSection s("UIManager::shadowTreeDidFinishTransaction");
if (delegate_ != nullptr) {
delegate_->uiManagerDidFinishTransaction(
std::move(mountingCoordinator), mountSynchronously);
}
}
void UIManager::reportMount(SurfaceId surfaceId) const {
TraceSection s("UIManager::reportMount");
auto time = HighResTimeStamp::now();
auto rootShadowNode = RootShadowNode::Shared{};
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
rootShadowNode =
shadowTree.getMountingCoordinator()->getBaseRevision().rootShadowNode;
});
{
std::shared_lock lock(mountHookMutex_);
for (auto* mountHook : mountHooks_) {
if (rootShadowNode) {
mountHook->shadowTreeDidMount(rootShadowNode, time);
} else {
mountHook->shadowTreeDidUnmount(surfaceId, time);
}
}
}
}
#pragma mark - UIManagerAnimationDelegate
void UIManager::setAnimationDelegate(UIManagerAnimationDelegate* delegate) {
animationDelegate_ = delegate;
}
void UIManager::stopSurfaceForAnimationDelegate(SurfaceId surfaceId) const {
if (animationDelegate_ != nullptr) {
animationDelegate_->stopSurface(surfaceId);
}
}
void UIManager::setNativeAnimatedDelegate(
std::weak_ptr<UIManagerNativeAnimatedDelegate> delegate) {
nativeAnimatedDelegate_ = delegate;
}
void UIManager::unstable_setAnimationBackend(
std::weak_ptr<UIManagerAnimationBackend> animationBackend) {
animationBackend_ = animationBackend;
}
std::weak_ptr<UIManagerAnimationBackend>
UIManager::unstable_getAnimationBackend() {
return animationBackend_;
}
void UIManager::animationTick() const {
if (animationDelegate_ != nullptr &&
animationDelegate_->shouldAnimateFrame()) {
shadowTreeRegistry_.enumerate([](const ShadowTree& shadowTree, bool&) {
shadowTree.notifyDelegatesOfUpdates();
});
}
if (auto nativeAnimatedDelegate = nativeAnimatedDelegate_.lock()) {
nativeAnimatedDelegate->runAnimationFrame();
}
}
void UIManager::synchronouslyUpdateViewOnUIThread(
Tag tag,
const folly::dynamic& props) {
if (delegate_ != nullptr) {
delegate_->uiManagerShouldSynchronouslyUpdateViewOnUIThread(tag, props);
}
}
#pragma mark - Add & Remove event listener
void UIManager::addEventListener(
std::shared_ptr<const EventListener> listener) {
if (delegate_ != nullptr) {
delegate_->uiManagerShouldAddEventListener(listener);
}
}
void UIManager::removeEventListener(
const std::shared_ptr<const EventListener>& listener) {
if (delegate_ != nullptr) {
delegate_->uiManagerShouldRemoveEventListener(listener);
}
}
void UIManager::setOnSurfaceStartCallback(
UIManagerDelegate::OnSurfaceStartCallback&& callback) {
if (delegate_ != nullptr) {
delegate_->uiManagerShouldSetOnSurfaceStartCallback(std::move(callback));
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,254 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <folly/dynamic.h>
#include <jsi/jsi.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <shared_mutex>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/core/InstanceHandle.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/StateData.h>
#include <react/renderer/leakchecker/LeakChecker.h>
#include <react/renderer/mounting/ShadowTree.h>
#include <react/renderer/mounting/ShadowTreeDelegate.h>
#include <react/renderer/mounting/ShadowTreeRegistry.h>
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
#include <react/renderer/uimanager/UIManagerDelegate.h>
#include <react/renderer/uimanager/UIManagerNativeAnimatedDelegate.h>
#include <react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
#include <react/renderer/uimanager/primitives.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
class UIManagerBinding;
class UIManagerCommitHook;
class UIManagerMountHook;
class UIManager final : public ShadowTreeDelegate {
public:
UIManager(const RuntimeExecutor &runtimeExecutor, std::shared_ptr<const ContextContainer> contextContainer);
~UIManager() override;
void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry &componentDescriptorRegistry);
/*
* Sets and gets the UIManager's delegate.
* The delegate is stored as a raw pointer, so the owner must null
* the pointer before being destroyed.
*/
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);
/**
* Sets and gets UIManager's AnimationBackend reference.
*/
void unstable_setAnimationBackend(std::weak_ptr<UIManagerAnimationBackend> animationBackend);
std::weak_ptr<UIManagerAnimationBackend> unstable_getAnimationBackend();
/**
* Execute stopSurface on any UIMAnagerAnimationDelegate.
*/
void stopSurfaceForAnimationDelegate(SurfaceId surfaceId) const;
void setNativeAnimatedDelegate(std::weak_ptr<UIManagerNativeAnimatedDelegate> delegate);
void animationTick() const;
void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props);
/*
* Provides access to a UIManagerBindging.
* The `callback` methods will not be called if the internal pointer to
* `UIManagerBindging` is `nullptr`.
* The callback is called synchronously on the same thread.
*/
void visitBinding(
const std::function<void(const UIManagerBinding &uiManagerBinding)> &callback,
jsi::Runtime &runtime) const;
/*
* Registers and unregisters a commit hook.
*/
void registerCommitHook(UIManagerCommitHook &commitHook);
void unregisterCommitHook(UIManagerCommitHook &commitHook);
/*
* Registers and unregisters a mount hook.
*/
void registerMountHook(UIManagerMountHook &mountHook);
void unregisterMountHook(UIManagerMountHook &mountHook);
std::shared_ptr<const ShadowNode> getNewestCloneOfShadowNode(const ShadowNode &shadowNode) const;
ShadowTreeRevisionConsistencyManager *getShadowTreeRevisionConsistencyManager();
ShadowTreeRevisionProvider *getShadowTreeRevisionProvider();
#pragma mark - Surface Start & Stop
void startSurface(
ShadowTree::Unique &&shadowTree,
const std::string &moduleName,
const folly::dynamic &props,
DisplayMode displayMode) const noexcept;
void startEmptySurface(ShadowTree::Unique &&shadowTree) const noexcept;
void setSurfaceProps(
SurfaceId surfaceId,
const std::string &moduleName,
const folly::dynamic &props,
DisplayMode displayMode) const noexcept;
ShadowTree::Unique stopSurface(SurfaceId surfaceId) const;
#pragma mark - ShadowTreeDelegate
void shadowTreeDidFinishTransaction(
std::shared_ptr<const MountingCoordinator> mountingCoordinator,
bool mountSynchronously) const override;
RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree &shadowTree,
const RootShadowNode::Shared &oldRootShadowNode,
const RootShadowNode::Unshared &newRootShadowNode,
const ShadowTree::CommitOptions &commitOptions) const override;
std::shared_ptr<ShadowNode> createNode(
Tag tag,
const std::string &componentName,
SurfaceId surfaceId,
RawProps props,
InstanceHandle::Shared instanceHandle) const;
std::shared_ptr<ShadowNode>
cloneNode(const ShadowNode &shadowNode, const ShadowNode::SharedListOfShared &children, RawProps rawProps) const;
void appendChild(
const std::shared_ptr<const ShadowNode> &parentShadowNode,
const std::shared_ptr<const ShadowNode> &childShadowNode) const;
void completeSurface(
SurfaceId surfaceId,
const ShadowNode::UnsharedListOfShared &rootChildren,
ShadowTree::CommitOptions commitOptions);
void setIsJSResponder(
const std::shared_ptr<const ShadowNode> &shadowNode,
bool isJSResponder,
bool blockNativeResponder) const;
std::shared_ptr<const ShadowNode> findNodeAtPoint(const std::shared_ptr<const ShadowNode> &shadowNode, Point point)
const;
/*
* Returns layout metrics of given `shadowNode` relative to
* `ancestorShadowNode` (relative to the root node in case if provided
* `ancestorShadowNode` is nullptr).
*/
LayoutMetrics getRelativeLayoutMetrics(
const ShadowNode &shadowNode,
const ShadowNode *ancestorShadowNode,
LayoutableShadowNode::LayoutInspectingPolicy policy) const;
/*
* Creates a new shadow node with given state data, clones what's necessary
* and performs a commit.
*/
void updateState(const StateUpdate &stateUpdate) const;
void dispatchCommand(
const std::shared_ptr<const ShadowNode> &shadowNode,
const std::string &commandName,
const folly::dynamic &args) const;
void setNativeProps_DEPRECATED(const std::shared_ptr<const ShadowNode> &shadowNode, RawProps rawProps) const;
void sendAccessibilityEvent(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &eventType);
/*
* Iterates over all shadow nodes which are parts of all registered surfaces
* and find the one that has given `tag`. Returns `nullptr` if the node
* wasn't found. This is a temporary workaround that should not be used in
* any core functionality.
*/
std::shared_ptr<const ShadowNode> findShadowNodeByTag_DEPRECATED(Tag tag) const;
const ShadowTreeRegistry &getShadowTreeRegistry() const;
void reportMount(SurfaceId surfaceId) const;
void updateShadowTree(std::unordered_map<Tag, folly::dynamic> &&tagToProps);
#pragma mark - Add & Remove event listener
void addEventListener(std::shared_ptr<const EventListener> listener);
void removeEventListener(const std::shared_ptr<const EventListener> &listener);
#pragma mark - Set on surface start callback
void setOnSurfaceStartCallback(UIManagerDelegate::OnSurfaceStartCallback &&callback);
private:
friend class UIManagerBinding;
friend class Scheduler;
friend class SurfaceHandler;
/**
* Configure a LayoutAnimation to happen on the next commit.
* This API configures a global LayoutAnimation starting from the root node.
*/
void configureNextLayoutAnimation(
jsi::Runtime &runtime,
const RawValue &config,
const jsi::Value &successCallback,
const jsi::Value &failureCallback) const;
std::shared_ptr<const ShadowNode> getShadowNodeInSubtree(
const ShadowNode &shadowNode,
const std::shared_ptr<const ShadowNode> &ancestorShadowNode) const;
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
UIManagerDelegate *delegate_{};
UIManagerAnimationDelegate *animationDelegate_{nullptr};
std::weak_ptr<UIManagerNativeAnimatedDelegate> nativeAnimatedDelegate_;
const RuntimeExecutor runtimeExecutor_{};
ShadowTreeRegistry shadowTreeRegistry_{};
std::shared_ptr<const ContextContainer> contextContainer_;
mutable std::shared_mutex commitHookMutex_;
mutable std::vector<UIManagerCommitHook *> commitHooks_;
mutable std::shared_mutex mountHookMutex_;
mutable std::vector<UIManagerMountHook *> mountHooks_;
std::unique_ptr<LeakChecker> leakChecker_;
std::unique_ptr<LazyShadowTreeRevisionConsistencyManager> lazyShadowTreeRevisionConsistencyManager_;
std::weak_ptr<UIManagerAnimationBackend> animationBackend_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/view/BaseViewProps.h>
#include <react/renderer/core/ShadowNodeFamily.h>
namespace facebook::react {
class UIManagerAnimationBackend {
public:
virtual ~UIManagerAnimationBackend() = default;
virtual void onAnimationFrame(double timestamp) = 0;
// TODO: T240293839 Move over start() function and mutation types
virtual void stop(bool isAsync) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <jsi/jsi.h>
#include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
#include <react/renderer/core/RawValue.h>
namespace facebook::react {
class UIManagerAnimationDelegate {
public:
virtual ~UIManagerAnimationDelegate() = default;
/*
* Configure a LayoutAnimation.
* TODO: need SurfaceId here
*/
virtual void uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime &runtime,
const RawValue &config,
const jsi::Value &successCallback,
const jsi::Value &failureCallback) const = 0;
/**
* Set ComponentDescriptor registry.
*
* @param componentDescriptorRegistry the registry of componentDescriptors
*/
virtual void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry &componentDescriptorRegistry) = 0;
/**
* Only needed on Android to drive animations.
*/
virtual bool shouldAnimateFrame() const = 0;
/**
* Drop any animations for a given surface.
*/
virtual void stopSurface(SurfaceId surfaceId) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,890 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "UIManagerBinding.h"
#include <cxxreact/TraceSection.h>
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/view/PointerEvent.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/dom/DOM.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/renderer/uimanager/primitives.h>
#include <utility>
namespace facebook::react {
void UIManagerBinding::createAndInstallIfNeeded(
jsi::Runtime& runtime,
const std::shared_ptr<UIManager>& uiManager) {
auto uiManagerModuleName = "nativeFabricUIManager";
auto uiManagerValue =
runtime.global().getProperty(runtime, uiManagerModuleName);
if (uiManagerValue.isUndefined()) {
// The global namespace does not have an instance of the binding;
// we need to create, install and return it.
auto uiManagerBinding = std::make_shared<UIManagerBinding>(uiManager);
auto object = jsi::Object::createFromHostObject(runtime, uiManagerBinding);
runtime.global().setProperty(
runtime, uiManagerModuleName, std::move(object));
}
}
std::shared_ptr<UIManagerBinding> UIManagerBinding::getBinding(
jsi::Runtime& runtime) {
auto uiManagerModuleName = "nativeFabricUIManager";
auto uiManagerValue =
runtime.global().getProperty(runtime, uiManagerModuleName);
if (uiManagerValue.isUndefined()) {
return nullptr;
}
auto uiManagerObject = uiManagerValue.asObject(runtime);
return uiManagerObject.getHostObject<UIManagerBinding>(runtime);
}
UIManagerBinding::UIManagerBinding(std::shared_ptr<UIManager> uiManager)
: uiManager_(std::move(uiManager)) {}
UIManagerBinding::~UIManagerBinding() {
LOG(WARNING) << "UIManagerBinding::~UIManagerBinding() was called (address: "
<< this << ").";
}
void UIManagerBinding::dispatchEvent(
jsi::Runtime& runtime,
const EventTarget* eventTarget,
const std::string& type,
ReactEventPriority priority,
const EventPayload& eventPayload) const {
TraceSection s("UIManagerBinding::dispatchEvent", "type", type);
if (eventPayload.getType() == EventPayloadType::PointerEvent) {
auto pointerEvent = static_cast<const PointerEvent&>(eventPayload);
auto dispatchCallback = [this, &runtime](
const ShadowNode& targetNode,
const std::string& type,
ReactEventPriority priority,
const EventPayload& eventPayload) {
auto eventTarget = targetNode.getEventEmitter()->getEventTarget();
if (eventTarget != nullptr) {
eventTarget->retain(runtime);
this->dispatchEventToJS(
runtime, eventTarget.get(), type, priority, eventPayload);
eventTarget->release(runtime);
}
};
auto targetNode = PointerEventsProcessor::getShadowNodeFromEventTarget(
runtime, eventTarget);
if (targetNode != nullptr) {
pointerEventsProcessor_.interceptPointerEvent(
targetNode,
type,
priority,
pointerEvent,
dispatchCallback,
*uiManager_);
}
} else {
dispatchEventToJS(runtime, eventTarget, type, priority, eventPayload);
}
}
void UIManagerBinding::dispatchEventToJS(
jsi::Runtime& runtime,
const EventTarget* eventTarget,
const std::string& type,
ReactEventPriority priority,
const EventPayload& eventPayload) const {
auto payload = eventPayload.asJSIValue(runtime);
// If a payload is null, the factory has decided to cancel the event
if (payload.isNull()) {
return;
}
auto instanceHandle = eventTarget != nullptr ? [&]() {
auto instanceHandle = eventTarget->getInstanceHandle(runtime);
if (instanceHandle.isUndefined()) {
return jsi::Value::null();
}
// Mixing `target` into `payload`.
if (!payload.isObject()) {
LOG(ERROR) << "payload for dispatchEvent is not an object: "
<< eventTarget->getTag();
}
react_native_assert(payload.isObject());
payload.asObject(runtime).setProperty(
runtime, "target", eventTarget->getTag());
return instanceHandle;
}()
: jsi::Value::null();
if (instanceHandle.isNull()) {
// Do not log all missing instanceHandles to avoid log spam
LOG_EVERY_N(INFO, 10) << "instanceHandle is null, event of type " << type
<< " will be dropped";
}
currentEventPriority_ = priority;
if (eventHandler_) {
eventHandler_->call(
runtime,
std::move(instanceHandle),
jsi::String::createFromUtf8(runtime, type),
std::move(payload));
}
currentEventPriority_ = ReactEventPriority::Default;
}
void UIManagerBinding::invalidate() const {
uiManager_->setDelegate(nullptr);
}
static void validateArgumentCount(
jsi::Runtime& runtime,
const std::string& methodName,
size_t expected,
size_t actual) {
if (actual < expected) {
throw jsi::JSError(
runtime,
methodName + " requires " + std::to_string(expected) +
" arguments, but only " + std::to_string(actual) + " were passed");
}
}
jsi::Value UIManagerBinding::get(
jsi::Runtime& runtime,
const jsi::PropNameID& name) {
auto methodName = name.utf8(runtime);
// Convert shared_ptr<UIManager> to a raw ptr
// Why? Because:
// 1) UIManagerBinding strongly retains UIManager. The JS VM
// strongly retains UIManagerBinding (through the JSI).
// These functions are JSI functions and are only called via
// the JS VM; if the JS VM is torn down, those functions can't
// execute and these lambdas won't execute.
// 2) The UIManager is only deallocated when all references to it
// are deallocated, including the UIManagerBinding. That only
// happens when the JS VM is deallocated. So, the raw pointer
// is safe.
//
// Even if it's safe, why not just use shared_ptr anyway as
// extra insurance?
// 1) Using shared_ptr or weak_ptr when they're not needed is
// a pessimisation. It's more instructions executed without
// any additional value in this case.
// 2) How and when exactly these lambdas is deallocated is
// complex. Adding shared_ptr to them which causes the UIManager
// to potentially live longer is unnecessary, complicated cognitive
// overhead.
// 3) There is a strong suspicion that retaining UIManager from
// these C++ lambdas, which are retained by an object that is held onto
// by the JSI, caused some crashes upon deallocation of the
// Scheduler and JS VM. This could happen if, for instance, C++
// semantics cause these lambda to not be deallocated until
// a CPU tick (or more) after the JS VM is deallocated.
UIManager* uiManager = uiManager_.get();
// Semantic: Creates a new node with given pieces.
if (methodName == "createNode") {
auto paramCount = 5;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
try {
validateArgumentCount(runtime, methodName, paramCount, count);
auto instanceHandle =
instanceHandleFromValue(runtime, arguments[4], arguments[0]);
if (!instanceHandle) {
react_native_assert(false);
return jsi::Value::undefined();
}
return valueFromShadowNode(
runtime,
uiManager->createNode(
tagFromValue(arguments[0]),
stringFromValue(runtime, arguments[1]),
surfaceIdFromValue(runtime, arguments[2]),
RawProps(runtime, arguments[3]),
std::move(instanceHandle)),
true);
} catch (const std::logic_error& ex) {
LOG(FATAL) << "logic_error in createNode: " << ex.what();
}
});
}
if (methodName == "setIsJSResponder") {
auto paramCount = 3;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
uiManager->setIsJSResponder(
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
arguments[1].getBool(),
arguments[2].getBool());
return jsi::Value::undefined();
});
}
if (methodName == "findNodeAtPoint") {
auto paramCount = 4;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) {
validateArgumentCount(runtime, methodName, paramCount, count);
auto node = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
auto locationX = (Float)arguments[1].getNumber();
auto locationY = (Float)arguments[2].getNumber();
auto onSuccessFunction =
arguments[3].getObject(runtime).getFunction(runtime);
auto targetNode = uiManager->findNodeAtPoint(
node, Point{.x = locationX, .y = locationY});
if (!targetNode) {
onSuccessFunction.call(runtime, jsi::Value::null());
return jsi::Value::undefined();
}
auto& eventTarget = targetNode->getEventEmitter()->eventTarget_;
EventEmitter::DispatchMutex().lock();
eventTarget->retain(runtime);
auto instanceHandle = eventTarget->getInstanceHandle(runtime);
eventTarget->release(runtime);
EventEmitter::DispatchMutex().unlock();
onSuccessFunction.call(runtime, std::move(instanceHandle));
return jsi::Value::undefined();
});
}
// Semantic: Clones the node with *same* props and *given* children.
if (methodName == "cloneNodeWithNewChildren") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
// TODO: re-enable when passChildrenWhenCloningPersistedNodes is
// rolled out
// validateArgumentCount(runtime, methodName, paramCount, count);
return valueFromShadowNode(
runtime,
uiManager->cloneNode(
*Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
count > 1 ? shadowNodeListFromValue(runtime, arguments[1])
: ShadowNode::emptySharedShadowNodeSharedList(),
RawProps()),
true);
});
}
// Semantic: Clones the node with *given* props and *same* children.
if (methodName == "cloneNodeWithNewProps") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
return valueFromShadowNode(
runtime,
uiManager->cloneNode(
*Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
nullptr,
RawProps(runtime, arguments[1])),
true);
});
}
// Semantic: Clones the node with *given* props and *given* children.
if (methodName == "cloneNodeWithNewChildrenAndProps") {
auto paramCount = 3;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
// TODO: re-enable when passChildrenWhenCloningPersistedNodes is
// rolled out
// validateArgumentCount(runtime, methodName, paramCount, count);
bool hasChildrenArg = count == 3;
return valueFromShadowNode(
runtime,
uiManager->cloneNode(
*Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
hasChildrenArg
? shadowNodeListFromValue(runtime, arguments[1])
: ShadowNode::emptySharedShadowNodeSharedList(),
RawProps(runtime, arguments[hasChildrenArg ? 2 : 1])),
true);
});
}
if (methodName == "appendChild") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
uiManager->appendChild(
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[1]));
return jsi::Value::undefined();
});
}
// TODO: remove when passChildrenWhenCloningPersistedNodes is rolled out
if (methodName == "createChildSet") {
return jsi::Function::createFromHostFunction(
runtime,
name,
0,
[](jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* /*arguments*/,
size_t /*count*/) -> jsi::Value {
auto shadowNodeList =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
std::vector<std::shared_ptr<const ShadowNode>>({}));
return valueFromShadowNodeList(runtime, shadowNodeList);
});
}
// TODO: remove when passChildrenWhenCloningPersistedNodes is rolled out
if (methodName == "appendChildToSet") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[0]);
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[1]);
shadowNodeList->push_back(shadowNode);
return jsi::Value::undefined();
});
}
if (methodName == "completeRoot") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto runtimeSchedulerBinding =
RuntimeSchedulerBinding::getBinding(runtime);
auto surfaceId = surfaceIdFromValue(runtime, arguments[0]);
auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[1]);
uiManager->completeSurface(
surfaceId,
shadowNodeList,
{.enableStateReconciliation = true,
.mountSynchronously = false,
.source = ShadowTree::CommitSource::React});
return jsi::Value::undefined();
});
}
if (methodName == "registerEventHandler") {
auto paramCount = 1;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[this, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto eventHandler =
arguments[0].getObject(runtime).getFunction(runtime);
eventHandler_ =
std::make_unique<jsi::Function>(std::move(eventHandler));
return jsi::Value::undefined();
});
}
if (methodName == "getRelativeLayoutMetrics") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
*Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[1])
.get(),
{/* .includeTransform = */ .includeTransform = false});
auto frame = layoutMetrics.frame;
auto result = jsi::Object(runtime);
result.setProperty(runtime, "left", frame.origin.x);
result.setProperty(runtime, "top", frame.origin.y);
result.setProperty(runtime, "width", frame.size.width);
result.setProperty(runtime, "height", frame.size.height);
return result;
});
}
if (methodName == "dispatchCommand") {
auto paramCount = 3;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
if (arguments[0].isObject()) {
auto shadowNode =
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
uiManager->dispatchCommand(
shadowNode,
stringFromValue(runtime, arguments[1]),
commandArgsFromValue(runtime, arguments[2]));
}
return jsi::Value::undefined();
});
}
if (methodName == "setNativeProps") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
uiManager->setNativeProps_DEPRECATED(
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
RawProps(runtime, arguments[1]));
return jsi::Value::undefined();
});
}
// Legacy API
if (methodName == "measureLayout") {
auto paramCount = 4;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
auto relativeToShadowNode =
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[1]);
auto onFailFunction =
arguments[2].getObject(runtime).getFunction(runtime);
auto onSuccessFunction =
arguments[3].getObject(runtime).getFunction(runtime);
auto currentRevision =
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
onFailFunction.call(runtime);
return jsi::Value::undefined();
}
auto maybeRect = dom::measureLayout(
currentRevision, *shadowNode, *relativeToShadowNode);
if (!maybeRect) {
onFailFunction.call(runtime);
return jsi::Value::undefined();
}
auto rect = maybeRect.value();
onSuccessFunction.call(
runtime,
{jsi::Value{runtime, rect.x},
jsi::Value{runtime, rect.y},
jsi::Value{runtime, rect.width},
jsi::Value{runtime, rect.height}});
return jsi::Value::undefined();
});
}
if (methodName == "measure") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
auto callbackFunction =
arguments[1].getObject(runtime).getFunction(runtime);
auto currentRevision =
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
callbackFunction.call(runtime, {0, 0, 0, 0, 0, 0});
return jsi::Value::undefined();
}
auto measureRect = dom::measure(currentRevision, *shadowNode);
callbackFunction.call(
runtime,
{jsi::Value{runtime, measureRect.x},
jsi::Value{runtime, measureRect.y},
jsi::Value{runtime, measureRect.width},
jsi::Value{runtime, measureRect.height},
jsi::Value{runtime, measureRect.pageX},
jsi::Value{runtime, measureRect.pageY}});
return jsi::Value::undefined();
});
}
if (methodName == "measureInWindow") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
auto callbackFunction =
arguments[1].getObject(runtime).getFunction(runtime);
auto currentRevision =
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
callbackFunction.call(runtime, {0, 0, 0, 0});
return jsi::Value::undefined();
}
auto rect = dom::measureInWindow(currentRevision, *shadowNode);
callbackFunction.call(
runtime,
{jsi::Value{runtime, rect.x},
jsi::Value{runtime, rect.y},
jsi::Value{runtime, rect.width},
jsi::Value{runtime, rect.height}});
return jsi::Value::undefined();
});
}
if (methodName == "sendAccessibilityEvent") {
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
uiManager->sendAccessibilityEvent(
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]),
stringFromValue(runtime, arguments[1]));
return jsi::Value::undefined();
});
}
if (methodName == "configureNextLayoutAnimation") {
auto paramCount = 3;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
uiManager->configureNextLayoutAnimation(
runtime,
// TODO: pass in JSI value instead of folly::dynamic to RawValue
RawValue(commandArgsFromValue(runtime, arguments[0])),
arguments[1],
arguments[2]);
return jsi::Value::undefined();
});
}
if (methodName == "unstable_getCurrentEventPriority") {
return jsi::Function::createFromHostFunction(
runtime,
name,
0,
[this](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*thisValue*/,
const jsi::Value* /*arguments*/,
size_t /*count*/) -> jsi::Value {
return {serialize(currentEventPriority_)};
});
}
if (methodName == "unstable_DefaultEventPriority") {
return {serialize(ReactEventPriority::Default)};
}
if (methodName == "unstable_DiscreteEventPriority") {
return {serialize(ReactEventPriority::Discrete)};
}
if (methodName == "unstable_ContinuousEventPriority") {
return {serialize(ReactEventPriority::Continuous)};
}
if (methodName == "unstable_IdleEventPriority") {
return {serialize(ReactEventPriority::Idle)};
}
if (methodName == "findShadowNodeByTag_DEPRECATED") {
auto paramCount = 1;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNode = uiManager->findShadowNodeByTag_DEPRECATED(
tagFromValue(arguments[0]));
if (!shadowNode) {
return jsi::Value::null();
}
return valueFromShadowNode(runtime, shadowNode);
});
}
if (methodName == "getBoundingClientRect") {
// This has been moved to `NativeDOM` but we need to keep it here because
// there are still some callsites using this method in apps that don't have
// the DOM APIs enabled yet.
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
bool includeTransform = arguments[1].getBool();
auto currentRevision =
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return jsi::Value::undefined();
}
auto domRect = dom::getBoundingClientRect(
currentRevision, *shadowNode, includeTransform);
return jsi::Array::createWithElements(
runtime,
jsi::Value{runtime, domRect.x},
jsi::Value{runtime, domRect.y},
jsi::Value{runtime, domRect.width},
jsi::Value{runtime, domRect.height});
});
}
if (methodName == "compareDocumentPosition") {
// This has been moved to `NativeDOM` but we need to keep it here because
// there are still some callsites using this method in apps that don't have
// the DOM APIs enabled yet.
auto paramCount = 2;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[0]);
auto otherShadowNode =
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, arguments[1]);
auto currentRevision =
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
shadowNode->getSurfaceId());
double documentPosition = 0;
if (currentRevision != nullptr) {
documentPosition = (double)dom::compareDocumentPosition(
currentRevision, *shadowNode, *otherShadowNode);
}
return {documentPosition};
});
}
return jsi::Value::undefined();
}
UIManager& UIManagerBinding::getUIManager() {
return *uiManager_;
}
PointerEventsProcessor& UIManagerBinding::getPointerEventsProcessor() {
return pointerEventsProcessor_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <folly/dynamic.h>
#include <jsi/jsi.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/uimanager/PointerEventsProcessor.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/primitives.h>
namespace facebook::react {
/*
* Exposes UIManager to JavaScript realm.
*/
class UIManagerBinding : public jsi::HostObject {
public:
/*
* Installs UIManagerBinding into JavaScript runtime if needed.
* Creates and sets `UIManagerBinding` into the global namespace.
* Thread synchronization must be enforced externally.
*/
static void createAndInstallIfNeeded(jsi::Runtime &runtime, const std::shared_ptr<UIManager> &uiManager);
/*
* Returns a pointer to UIManagerBinding previously installed into a runtime.
* Thread synchronization must be enforced externally.
*/
static std::shared_ptr<UIManagerBinding> getBinding(jsi::Runtime &runtime);
UIManagerBinding(std::shared_ptr<UIManager> uiManager);
~UIManagerBinding() override;
/*
* Delivers raw event data to JavaScript.
* Thread synchronization must be enforced externally.
*/
void dispatchEvent(
jsi::Runtime &runtime,
const EventTarget *eventTarget,
const std::string &type,
ReactEventPriority priority,
const EventPayload &payload) const;
/*
* Invalidates the binding and underlying UIManager.
* Allows to save some resources and prevents UIManager's delegate to be
* called.
* Calling public methods of this class after calling this method is UB.
* Can be called on any thread.
*/
void invalidate() const;
/*
* `jsi::HostObject` specific overloads.
*/
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;
UIManager &getUIManager();
PointerEventsProcessor &getPointerEventsProcessor();
private:
/*
* Internal method that sends the event to JS. Should only be called from
* UIManagerBinding::dispatchEvent.
*/
void dispatchEventToJS(
jsi::Runtime &runtime,
const EventTarget *eventTarget,
const std::string &type,
ReactEventPriority priority,
const EventPayload &payload) const;
std::shared_ptr<UIManager> uiManager_;
std::unique_ptr<jsi::Function> eventHandler_;
mutable PointerEventsProcessor pointerEventsProcessor_;
mutable ReactEventPriority currentEventPriority_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/root/RootShadowNode.h>
namespace facebook::react {
class ShadowTree;
struct ShadowTreeCommitOptions;
class UIManager;
/*
* Implementing a commit hook allows to observe and alter Shadow Tree commits.
*/
class UIManagerCommitHook {
public:
/*
* Called right after the commit hook is registered or unregistered.
*/
virtual void commitHookWasRegistered(const UIManager &uiManager) noexcept = 0;
virtual void commitHookWasUnregistered(const UIManager &uiManager) noexcept = 0;
/*
* Called right before a `ShadowTree` commits a new tree.
* The semantic of the method corresponds to a method of the same name
* from `ShadowTreeDelegate`.
*/
virtual RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree &shadowTree,
const RootShadowNode::Shared &oldRootShadowNode,
const RootShadowNode::Unshared &newRootShadowNode,
const ShadowTreeCommitOptions & /*commitOptions*/) noexcept
{
return shadowTreeWillCommit(shadowTree, oldRootShadowNode, newRootShadowNode);
}
/*
* This is a version of `shadowTreeWillCommit` without `commitOptions` for
* backward compatibility.
*/
virtual RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree & /*shadowTree*/,
const RootShadowNode::Shared & /*oldRootShadowNode*/,
const RootShadowNode::Unshared &newRootShadowNode) noexcept
{
// No longer a pure method as subclasses are expected to implement the other
// flavor instead.
return newRootShadowNode;
}
virtual ~UIManagerCommitHook() noexcept = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/mounting/MountingCoordinator.h>
#include <react/renderer/mounting/ShadowTree.h>
namespace facebook::react {
/*
* Abstract class for UIManager's delegate.
*/
class UIManagerDelegate {
public:
/*
* Called right after a new/updated Shadow Node tree is constructed.
* For this moment the tree is already laid out and sealed.
*/
virtual void uiManagerDidFinishTransaction(
std::shared_ptr<const MountingCoordinator> mountingCoordinator,
bool mountSynchronously) = 0;
/*
* Called each time when UIManager constructs a new Shadow Node. Receiver
* might use this to optimistically allocate a new native view
* instances.
*/
virtual void uiManagerDidCreateShadowNode(const ShadowNode &shadowNode) = 0;
/*
* Called when UIManager wants to dispatch a command to the mounting layer.
*/
virtual void uiManagerDidDispatchCommand(
const std::shared_ptr<const ShadowNode> &shadowNode,
const std::string &commandName,
const folly::dynamic &args) = 0;
/*
* Called when UIManager wants to dispatch some accessibility event
* to the mounting layer. eventType is platform-specific and not all
* platforms will necessarily implement the same set of events.
*/
virtual void uiManagerDidSendAccessibilityEvent(
const std::shared_ptr<const ShadowNode> &shadowNode,
const std::string &eventType) = 0;
/*
* Set JS responder for a view.
*/
virtual void uiManagerDidSetIsJSResponder(
const std::shared_ptr<const ShadowNode> &shadowNode,
bool isJSResponder,
bool blockNativeResponder) = 0;
/*
* Synchronous view update.
*/
virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0;
/*
* Called after updateShadowTree is invoked.
*/
virtual void uiManagerDidUpdateShadowTree(const std::unordered_map<Tag, folly::dynamic> &tagToProps) = 0;
/*
* Add event listener.
*/
virtual void uiManagerShouldAddEventListener(std::shared_ptr<const EventListener> listener) = 0;
/*
* Remove event listener.
*/
virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr<const EventListener> &listener) = 0;
/*
* Start surface.
*/
virtual void uiManagerDidStartSurface(const ShadowTree &shadowTree) = 0;
using OnSurfaceStartCallback = std::function<void(const ShadowTree &shadowTree)>;
virtual void uiManagerShouldSetOnSurfaceStartCallback(OnSurfaceStartCallback &&callback) = 0;
virtual ~UIManagerDelegate() noexcept = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/root/RootShadowNode.h>
#include "UIManager.h"
namespace facebook::react {
class ShadowTree;
class UIManager;
/*
* Implementing a mount hook allows to observe Shadow Trees being mounted in
* the host platform.
*/
class UIManagerMountHook {
public:
/*
* Called right after a `ShadowTree` is mounted in the host platform.
*/
virtual void shadowTreeDidMount(
const RootShadowNode::Shared &rootShadowNode,
HighResTimeStamp mountTime) noexcept = 0;
virtual void shadowTreeDidUnmount(SurfaceId /*surfaceId*/, HighResTimeStamp /*unmountTime*/) noexcept
{
// Default no-op implementation for backwards compatibility.
}
virtual ~UIManagerMountHook() noexcept = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <jsi/jsi.h>
#include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
#include <react/renderer/core/RawValue.h>
namespace facebook::react {
class UIManagerNativeAnimatedDelegate {
public:
virtual ~UIManagerNativeAnimatedDelegate() = default;
virtual void runAnimationFrame() = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,228 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "UIManager.h"
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ShadowNode.h>
#include <deque>
#include <memory>
#include <optional>
#include <vector>
namespace facebook::react {
namespace {
struct ShadowNodeUpdateInfo {
Tag tag;
int depth;
std::weak_ptr<const ShadowNode> node;
// populate for node that needs props update
std::optional<folly::dynamic> changedProps{};
// populate for node that's also a ancestor node for updated nodes
std::vector<int> updatedChildrenIndices{};
};
void addAncestorsToUpdateList(
std::shared_ptr<const ShadowNode>& shadowNode,
std::shared_ptr<const RootShadowNode>& rootShadowNode,
std::vector<ShadowNodeUpdateInfo>& shadowNodesToUpdate) {
// list of ancestors from root ShadowNode to current ShadowNode's parent
auto ancestors = shadowNode->getFamily().getAncestors(*rootShadowNode);
std::vector<std::shared_ptr<const ShadowNode>> ancestorShadowNodesShared{};
ancestorShadowNodesShared.resize(ancestors.size());
std::shared_ptr<const ShadowNode> currentShadowNode = rootShadowNode;
auto ancestorIndex = 0;
while (!currentShadowNode->getChildren().empty() &&
currentShadowNode->getTag() != shadowNode->getTag()) {
ancestorShadowNodesShared[ancestorIndex] = currentShadowNode;
auto children = currentShadowNode->getChildren();
auto childIndex = ancestors[ancestorIndex].second;
currentShadowNode = children[childIndex];
ancestorIndex++;
}
int ancestorDepth = static_cast<int>(ancestors.size() - 1);
// iterate from current ShadowNode's parent to root ShadowNode
for (auto iter = ancestors.rbegin(); iter != ancestors.rend(); ++iter) {
auto& ancestorShadowNode = iter->first.get();
auto ancestorTag = ancestorShadowNode.getTag();
auto ancestorAddedToUpdateList = std::find_if(
shadowNodesToUpdate.begin(),
shadowNodesToUpdate.end(),
[ancestorTag](const auto& elem) { return elem.tag == ancestorTag; });
if (ancestorAddedToUpdateList == shadowNodesToUpdate.end()) {
react_native_assert(
ancestorShadowNodesShared[ancestorDepth]->getTag() ==
ancestorShadowNode.getTag());
shadowNodesToUpdate.push_back({
.tag = ancestorShadowNode.getTag(),
.depth = ancestorDepth,
.node = ancestorShadowNodesShared[ancestorDepth],
.updatedChildrenIndices = {iter->second},
});
} else {
ancestorAddedToUpdateList->updatedChildrenIndices.push_back(iter->second);
}
ancestorDepth--;
}
}
} // namespace
/* Commit a map of ShadowNode props to ShadowTree, with guarantee that each
* ShadowNode is cloned only once in the commit.
* The tree cloning algorithm is inspired by `cloneShadowTreeWithNewProps` in
* https://github.com/software-mansion/react-native-reanimated/ (under MIT
* license).
*/
void UIManager::updateShadowTree(
std::unordered_map<Tag, folly::dynamic>&& tagToProps) {
const auto& contextContainer = *contextContainer_;
auto remainingTagToProps = std::move(tagToProps);
if (delegate_ != nullptr) {
delegate_->uiManagerDidUpdateShadowTree(remainingTagToProps);
}
getShadowTreeRegistry().enumerate([&](const ShadowTree& shadowTree,
bool& stop) {
if (remainingTagToProps.empty()) {
stop = true;
return;
}
auto rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
auto surfaceId = rootShadowNode->getSurfaceId();
std::vector<ShadowNodeUpdateInfo> shadowNodesToUpdate;
// Step 1: Create a list of shadow nodes to update
// which includes all the ShadowNodes of tags in input map, plus all
// their ancestors
std::deque<std::shared_ptr<const ShadowNode>> deque{rootShadowNode};
int depth = 0;
while (!deque.empty()) {
auto size = deque.size();
for (auto i = 0; i < size; i++) {
auto shadowNode = std::move(deque.front());
deque.pop_front();
if (auto nodesPropsIt = remainingTagToProps.find(shadowNode->getTag());
nodesPropsIt != remainingTagToProps.end()) {
auto shadowNodeTag = shadowNode->getTag();
auto shadowNodeAddedToUpdateList = std::find_if(
shadowNodesToUpdate.begin(),
shadowNodesToUpdate.end(),
[shadowNodeTag](const auto& elem) {
return elem.tag == shadowNodeTag;
});
if (shadowNodeAddedToUpdateList == shadowNodesToUpdate.end()) {
shadowNodesToUpdate.push_back(
{.tag = shadowNodeTag,
.depth = depth,
.node = shadowNode,
.changedProps = nodesPropsIt->second});
} else {
shadowNodeAddedToUpdateList->changedProps = nodesPropsIt->second;
}
remainingTagToProps.erase(nodesPropsIt->first);
// Add all its ancestors to shadowNodesToUpdate list
addAncestorsToUpdateList(
shadowNode, rootShadowNode, shadowNodesToUpdate);
}
for (const auto& child : shadowNode->getChildren()) {
deque.push_back(child);
}
}
depth++;
}
if (shadowNodesToUpdate.empty()) {
return;
}
// Step 2: Clone nodes from children to ancestors
std::sort(
shadowNodesToUpdate.begin(),
shadowNodesToUpdate.end(),
[](const auto& a, const auto& b) { return a.depth > b.depth; });
std::unordered_map<Tag, std::shared_ptr<ShadowNode>> clonedShadowNodes;
for (auto& nodeUpdateInfo : shadowNodesToUpdate) {
if (auto oldShadowNode = nodeUpdateInfo.node.lock()) {
Props::Shared newProps;
if (nodeUpdateInfo.changedProps) {
PropsParserContext propsParserContext{surfaceId, contextContainer};
auto componentDescriptor =
componentDescriptorRegistry_
->findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN(
oldShadowNode->getComponentHandle());
newProps = componentDescriptor->cloneProps(
propsParserContext,
oldShadowNode->getProps(),
RawProps(nodeUpdateInfo.changedProps.value()));
} else {
newProps = oldShadowNode->getProps();
}
ShadowNodeFragment fragment;
auto children = oldShadowNode->getChildren();
// If children are previously updated (children should be cloned and
// updated before parents), add it to the children list of ShadowNode
for (auto& updatedChildIndex : nodeUpdateInfo.updatedChildrenIndices) {
if (updatedChildIndex < children.size()) {
auto childTag = children[updatedChildIndex]->getTag();
auto clonedShadowNodesIt = clonedShadowNodes.find(childTag);
if (clonedShadowNodesIt != clonedShadowNodes.end()) {
children[updatedChildIndex] = clonedShadowNodesIt->second;
} else {
LOG(ERROR) << "Child ShadowNode has not been cloned";
}
} else {
LOG(WARNING) << "Child no longer exits";
}
}
auto cloned = oldShadowNode->clone(
{.props = newProps,
.children = std::make_shared<
std::vector<std::shared_ptr<const ShadowNode>>>(children)});
clonedShadowNodes.insert({oldShadowNode->getTag(), std::move(cloned)});
} else {
LOG(ERROR) << "oldShadowNode is null";
continue;
}
}
// Step 3: Commit ShadowTree
if (auto it = clonedShadowNodes.find(rootShadowNode->getTag());
it != clonedShadowNodes.end()) {
shadowTree.commit(
[rootNode =
it->second](const RootShadowNode&) -> RootShadowNode::Unshared {
return std::static_pointer_cast<RootShadowNode>(rootNode);
},
{});
} else {
LOG(ERROR) << "Root ShadowNode has not been cloned";
}
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_uimanager_consistency_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_renderer_uimanager_consistency OBJECT ${react_renderer_uimanager_consistency_SRC})
target_include_directories(react_renderer_uimanager_consistency PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_uimanager_consistency
glog
rrc_root
react_renderer_consistency
react_renderer_mounting)
target_compile_reactnative_options(react_renderer_uimanager_consistency PRIVATE)
target_compile_options(react_renderer_uimanager_consistency PRIVATE -Wpedantic)

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LazyShadowTreeRevisionConsistencyManager.h"
#include <glog/logging.h>
namespace facebook::react {
LazyShadowTreeRevisionConsistencyManager::
LazyShadowTreeRevisionConsistencyManager(
ShadowTreeRegistry& shadowTreeRegistry)
: shadowTreeRegistry_(shadowTreeRegistry) {}
void LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision(
SurfaceId surfaceId,
RootShadowNode::Shared rootShadowNode) {
std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_);
// We don't need to store the revision if we haven't locked.
// We can resolve lazily when requested.
if (lockCount > 0) {
capturedRootShadowNodesForConsistency_[surfaceId] =
std::move(rootShadowNode);
}
}
#pragma mark - ShadowTreeRevisionProvider
RootShadowNode::Shared
LazyShadowTreeRevisionConsistencyManager::getCurrentRevision(
SurfaceId surfaceId) {
{
std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_);
if (lockCount > 0) {
auto it = capturedRootShadowNodesForConsistency_.find(surfaceId);
if (it != capturedRootShadowNodesForConsistency_.end()) {
return it->second;
}
}
}
// This method is only going to be called from JS, so we don't need to protect
// the access to the shadow tree registry as well.
// If this was multi-threaded, we would need to protect it to avoid capturing
// root shadow nodes concurrently.
RootShadowNode::Shared rootShadowNode;
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
{
std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_);
if (lockCount > 0) {
capturedRootShadowNodesForConsistency_[surfaceId] = rootShadowNode;
}
}
return rootShadowNode;
}
#pragma mark - ConsistentShadowTreeRevisionProvider
void LazyShadowTreeRevisionConsistencyManager::lockRevisions() {
std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_);
// We actually capture the state lazily the first time we access it, so we
// don't need to do anything here.
lockCount++;
}
void LazyShadowTreeRevisionConsistencyManager::unlockRevisions() {
std::unique_lock lock(capturedRootShadowNodesForConsistencyMutex_);
if (lockCount == 0) {
LOG(WARNING)
<< "LazyShadowTreeRevisionConsistencyManager::unlockRevisions() called without a previous lock";
} else {
lockCount--;
}
if (lockCount == 0) {
capturedRootShadowNodesForConsistency_.clear();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/mounting/ShadowTreeRegistry.h>
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
#include <cstdint>
#include <memory>
#include <shared_mutex>
namespace facebook::react {
/**
* This class implements UI consistency for the JavaScript thread.
* This implementation forces JavaScript to see a stable revision of the shadow
* tree for a given surface ID, only updating it when React commits a new tree
* or between JS tasks.
*/
class LazyShadowTreeRevisionConsistencyManager : public ShadowTreeRevisionConsistencyManager,
public ShadowTreeRevisionProvider {
public:
explicit LazyShadowTreeRevisionConsistencyManager(ShadowTreeRegistry &shadowTreeRegistry);
void updateCurrentRevision(SurfaceId surfaceId, RootShadowNode::Shared rootShadowNode);
#pragma mark - ShadowTreeRevisionProvider
RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) override;
#pragma mark - ShadowTreeRevisionConsistencyManager
void lockRevisions() override;
void unlockRevisions() override;
private:
std::mutex capturedRootShadowNodesForConsistencyMutex_;
std::unordered_map<SurfaceId, RootShadowNode::Shared> capturedRootShadowNodesForConsistency_;
ShadowTreeRegistry &shadowTreeRegistry_;
uint_fast32_t lockCount{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <memory>
namespace facebook::react {
/**
* This interface is used for UI consistency, indicating the revision of the
* shadow tree that a caller should have access to.
*/
class ShadowTreeRevisionProvider {
public:
virtual ~ShadowTreeRevisionProvider() = default;
virtual RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,396 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/element/testUtils.h>
#include <react/renderer/mounting/ShadowTree.h>
#include <react/renderer/mounting/ShadowTreeRegistry.h>
#include <react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h>
namespace facebook::react {
class FakeShadowTreeDelegate : public ShadowTreeDelegate {
public:
RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree& /*shadowTree*/,
const std::shared_ptr<const RootShadowNode>& /*oldRootShadowNode*/,
const RootShadowNode::Unshared& newRootShadowNode,
const ShadowTree::CommitOptions& /*commitOptions*/) const override {
return newRootShadowNode;
}
void shadowTreeDidFinishTransaction(
std::shared_ptr<const MountingCoordinator> mountingCoordinator,
bool /*mountSynchronously*/) const override {}
};
class LazyShadowTreeRevisionConsistencyManagerTest : public ::testing::Test {
public:
LazyShadowTreeRevisionConsistencyManagerTest()
: consistencyManager_(shadowTreeRegistry_) {}
void TearDown() override {
// this is necessary because otherwise the test will crash with an assertion
// preventing the deallocation of the registry with registered shadow trees.
auto ids = std::vector<SurfaceId>();
shadowTreeRegistry_.enumerate(
[&ids](const ShadowTree& shadowTree, bool& /*stop*/) {
ids.push_back(shadowTree.getSurfaceId());
});
for (auto id : ids) {
shadowTreeRegistry_.remove(id);
}
}
std::unique_ptr<ShadowTree> createShadowTree(SurfaceId surfaceId) {
return std::make_unique<ShadowTree>(
surfaceId,
layoutConstraints_,
layoutContext_,
shadowTreeDelegate_,
contextContainer_);
}
ShadowTreeRegistry shadowTreeRegistry_{};
LazyShadowTreeRevisionConsistencyManager consistencyManager_;
LayoutConstraints layoutConstraints_{};
LayoutContext layoutContext_{};
FakeShadowTreeDelegate shadowTreeDelegate_{};
ContextContainer contextContainer_{};
};
TEST_F(LazyShadowTreeRevisionConsistencyManagerTest, testLockedOnNoRevision) {
consistencyManager_.lockRevisions();
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
shadowTreeRegistry_.add(createShadowTree(0));
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
consistencyManager_.unlockRevisions();
}
TEST_F(LazyShadowTreeRevisionConsistencyManagerTest, testNotLocked) {
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
shadowTreeRegistry_.add(createShadowTree(0));
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), newRootShadowNode);
}
TEST_F(
LazyShadowTreeRevisionConsistencyManagerTest,
testLockedOnNoRevisionWithUpdate) {
consistencyManager_.lockRevisions();
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
shadowTreeRegistry_.add(createShadowTree(0));
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
consistencyManager_.updateCurrentRevision(0, newRootShadowNode);
EXPECT_NE(consistencyManager_.getCurrentRevision(0), nullptr);
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
consistencyManager_.unlockRevisions();
}
TEST_F(
LazyShadowTreeRevisionConsistencyManagerTest,
testLockedOnNoRevisionWithMultipleUpdates) {
consistencyManager_.lockRevisions();
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
shadowTreeRegistry_.add(createShadowTree(0));
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
consistencyManager_.updateCurrentRevision(0, newRootShadowNode);
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
auto newRootShadowNode2 = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode2](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode2;
},
{});
});
consistencyManager_.updateCurrentRevision(0, newRootShadowNode2);
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(),
newRootShadowNode2.get());
consistencyManager_.unlockRevisions();
}
TEST_F(
LazyShadowTreeRevisionConsistencyManagerTest,
testLockedOnExistingRevision) {
shadowTreeRegistry_.add(createShadowTree(0));
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
consistencyManager_.lockRevisions();
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
consistencyManager_.unlockRevisions();
}
TEST_F(
LazyShadowTreeRevisionConsistencyManagerTest,
testLockedOnExistingRevisionWithUpdates) {
shadowTreeRegistry_.add(createShadowTree(0));
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
consistencyManager_.lockRevisions();
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
auto newRootShadowNode2 = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode2](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode2;
},
{});
});
// Not updated
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
consistencyManager_.updateCurrentRevision(0, newRootShadowNode2);
// Updated
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(),
newRootShadowNode2.get());
consistencyManager_.unlockRevisions();
}
TEST_F(LazyShadowTreeRevisionConsistencyManagerTest, testLockAfterUnlock) {
shadowTreeRegistry_.add(createShadowTree(0));
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
consistencyManager_.lockRevisions();
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
auto newRootShadowNode2 = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode2](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode2;
},
{});
});
// Not updated
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
consistencyManager_.unlockRevisions();
consistencyManager_.lockRevisions();
// Updated
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(),
newRootShadowNode2.get());
consistencyManager_.unlockRevisions();
}
TEST_F(LazyShadowTreeRevisionConsistencyManagerTest, testUpdateToUnmounted) {
shadowTreeRegistry_.add(createShadowTree(0));
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
consistencyManager_.lockRevisions();
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
consistencyManager_.updateCurrentRevision(0, nullptr);
// Updated
EXPECT_EQ(consistencyManager_.getCurrentRevision(0).get(), nullptr);
consistencyManager_.unlockRevisions();
}
TEST_F(LazyShadowTreeRevisionConsistencyManagerTest, testReentrance) {
consistencyManager_.lockRevisions();
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
shadowTreeRegistry_.add(createShadowTree(0));
auto element = Element<RootShadowNode>();
auto builder = simpleComponentBuilder();
auto newRootShadowNode = builder.build(element);
shadowTreeRegistry_.visit(
0, [newRootShadowNode](const ShadowTree& shadowTree) {
shadowTree.commit(
[&](const RootShadowNode& /*oldRootShadowNode*/) {
return newRootShadowNode;
},
{});
});
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
// Re-entrance
consistencyManager_.lockRevisions();
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
// Exit second lock
consistencyManager_.unlockRevisions();
EXPECT_EQ(consistencyManager_.getCurrentRevision(0), nullptr);
// Exit first lock
consistencyManager_.unlockRevisions();
// Updated!
EXPECT_EQ(
consistencyManager_.getCurrentRevision(0).get(), newRootShadowNode.get());
}
} // namespace facebook::react

View File

@@ -0,0 +1,158 @@
/*
* Copyright (c) Meta Platforms, Inc. and 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 <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/bridging/bridging.h>
#include <react/renderer/core/ShadowNode.h>
namespace facebook::react {
using BackgroundExecutor = std::function<void(std::function<void()> &&callback)>;
struct ShadowNodeListWrapper : public jsi::NativeState {
ShadowNodeListWrapper(ShadowNode::UnsharedListOfShared shadowNodeList) : shadowNodeList(std::move(shadowNodeList)) {}
// The below method needs to be implemented out-of-line in order for the class
// to have at least one "key function" (see
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vague-vtable)
~ShadowNodeListWrapper() override;
ShadowNode::UnsharedListOfShared shadowNodeList;
};
inline static jsi::Value valueFromShadowNode(
jsi::Runtime &runtime,
std::shared_ptr<const ShadowNode> shadowNode,
bool assignRuntimeShadowNodeReference = false)
{
// Wrap the shadow node so that we can update JS references from native
auto wrappedShadowNode = std::make_shared<ShadowNodeWrapper>(std::move(shadowNode));
if (assignRuntimeShadowNodeReference) {
wrappedShadowNode->shadowNode->setRuntimeShadowNodeReference(wrappedShadowNode);
}
jsi::Object obj(runtime);
obj.setNativeState(runtime, std::move(wrappedShadowNode));
return obj;
}
// TODO: once we no longer need to mutate the return value (appendChildToSet)
// make this a SharedListOfShared
inline static ShadowNode::UnsharedListOfShared shadowNodeListFromValue(jsi::Runtime &runtime, const jsi::Value &value)
{
// TODO: cleanup when passChildrenWhenCloningPersistedNodes is rolled out
jsi::Object object = value.asObject(runtime);
if (object.isArray(runtime)) {
auto jsArray = std::move(object).asArray(runtime);
size_t jsArrayLen = jsArray.length(runtime);
if (jsArrayLen > 0) {
auto shadowNodeArray = std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>();
shadowNodeArray->reserve(jsArrayLen);
for (size_t i = 0; i < jsArrayLen; i++) {
shadowNodeArray->push_back(
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(runtime, jsArray.getValueAtIndex(runtime, i)));
}
return shadowNodeArray;
} else {
// TODO: return ShadowNode::emptySharedShadowNodeSharedList()
return std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
std::vector<std::shared_ptr<const ShadowNode>>({}));
;
}
} else {
return object.getNativeState<ShadowNodeListWrapper>(runtime)->shadowNodeList;
}
}
inline static jsi::Value valueFromShadowNodeList(jsi::Runtime &runtime, ShadowNode::UnsharedListOfShared shadowNodeList)
{
auto wrapper = std::make_shared<ShadowNodeListWrapper>(std::move(shadowNodeList));
// Use the wrapper for NativeState too, otherwise we can't implement
// the marker interface. Could be simplified to a simple struct wrapper.
jsi::Object obj(runtime);
obj.setNativeState(runtime, std::move(wrapper));
return obj;
}
inline static ShadowNode::UnsharedListOfShared shadowNodeListFromWeakList(
const ShadowNode::UnsharedListOfWeak &weakShadowNodeList)
{
auto result = std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>();
for (const auto &weakShadowNode : *weakShadowNodeList) {
auto sharedShadowNode = weakShadowNode.lock();
if (!sharedShadowNode) {
return nullptr;
}
result->push_back(sharedShadowNode);
}
return result;
}
inline static ShadowNode::UnsharedListOfWeak weakShadowNodeListFromValue(jsi::Runtime &runtime, const jsi::Value &value)
{
auto shadowNodeList = shadowNodeListFromValue(runtime, value);
auto weakShadowNodeList = std::make_shared<std::vector<std::weak_ptr<const ShadowNode>>>();
for (const auto &shadowNode : *shadowNodeList) {
weakShadowNodeList->push_back(shadowNode);
}
return weakShadowNodeList;
}
inline static Tag tagFromValue(const jsi::Value &value)
{
return (Tag)value.getNumber();
}
inline static InstanceHandle::Shared
instanceHandleFromValue(jsi::Runtime &runtime, const jsi::Value &instanceHandleValue, const jsi::Value &tagValue)
{
react_native_assert(!instanceHandleValue.isNull());
if (instanceHandleValue.isNull()) {
return nullptr;
}
return std::make_shared<InstanceHandle>(runtime, instanceHandleValue, tagFromValue(tagValue));
}
inline static SurfaceId surfaceIdFromValue(jsi::Runtime &runtime, const jsi::Value &value)
{
return (SurfaceId)value.getNumber();
}
inline static int displayModeToInt(const DisplayMode value)
{
// the result of this method should be in sync with
// Libraries/ReactNative/DisplayMode.js
switch (value) {
case DisplayMode::Visible:
return 1;
case DisplayMode::Suspended:
return 2;
case DisplayMode::Hidden:
return 3;
default:
react_native_assert(0 && "displayModeToInt: Invalid DisplayMode");
return -1;
}
}
inline static std::string stringFromValue(jsi::Runtime &runtime, const jsi::Value &value)
{
return value.getString(runtime).utf8(runtime);
}
inline static folly::dynamic commandArgsFromValue(jsi::Runtime &runtime, const jsi::Value &value)
{
return jsi::dynamicFromValue(runtime, value);
}
} // namespace facebook::react

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <gtest/gtest.h>
#include <react/renderer/uimanager/UIManager.h>
using namespace facebook::react;
TEST(UIManagerTest, testSomething) {
// TODO
}

View File

@@ -0,0 +1,398 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <jsi/jsi.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
#include <react/renderer/uimanager/PointerEventsProcessor.h>
#include <react/renderer/uimanager/PointerHoverTracker.h>
#include <react/renderer/uimanager/UIManager.h>
namespace facebook::react {
struct PointerEventTestLogEntry {
Tag tag;
std::string eventName;
PointerEvent payload;
};
using EventLog = std::vector<PointerEventTestLogEntry>;
static inline void listenToAllPointerEvents(ViewProps& props) {
props.events[ViewEvents::Offset::PointerDown] = true;
props.events[ViewEvents::Offset::PointerMove] = true;
props.events[ViewEvents::Offset::PointerUp] = true;
props.events[ViewEvents::Offset::PointerEnter] = true;
props.events[ViewEvents::Offset::PointerLeave] = true;
props.events[ViewEvents::Offset::PointerOver] = true;
props.events[ViewEvents::Offset::PointerOut] = true;
}
class PointerEventsProcessorTest : public ::testing::Test {
public:
PointerEventsProcessorTest() {
auto contextContainer = std::make_shared<ContextContainer>();
ComponentDescriptorProviderRegistry componentDescriptorProviderRegistry{};
auto eventDispatcher = EventDispatcher::Shared{};
auto componentDescriptorRegistry =
componentDescriptorProviderRegistry.createComponentDescriptorRegistry(
ComponentDescriptorParameters{
.eventDispatcher = eventDispatcher,
.contextContainer = std::move(contextContainer),
.flavor = nullptr});
componentDescriptorProviderRegistry.add(
concreteComponentDescriptorProvider<RootComponentDescriptor>());
componentDescriptorProviderRegistry.add(
concreteComponentDescriptorProvider<ViewComponentDescriptor>());
auto builder = ComponentBuilder{componentDescriptorRegistry};
// Set up UIManager (with no-op executors since we don't need them for
// tests)
RuntimeExecutor runtimeExecutor =
[](std::function<void(facebook::jsi::Runtime & runtime)>&& callback) {};
uiManager_ = std::make_unique<UIManager>(runtimeExecutor, contextContainer);
uiManager_->setComponentDescriptorRegistry(componentDescriptorRegistry);
// Create a hierarchy of nodes
/*
* Test Hierarchy:
* ┌────────────────────┐
* │ROOT │
* │ ┌───────┬──────┐ │
* │ │A │B │ │
* │ │ ┌────┼────┐ │ │
* │ │ │AA │BB │ │ │
* │ │ │ │ │ │ │
* │ │ └────┼────┘ │ │
* │ └───────┴──────┘ │
* └────────────────────┘
*/
// clang-format off
auto elementRoot =
Element<RootShadowNode>()
.tag(1)
.surfaceId(surfaceId_)
.reference(rootNode_)
.props([] {
auto sharedProps = std::make_shared<RootProps>();
auto &props = *sharedProps;
listenToAllPointerEvents(props);
props.layoutConstraints = LayoutConstraints{.minimumSize={.width=0,.height=0}, .maximumSize={.width=500, .height=500}};
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(400));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(400));
yogaStyle.setDisplay(yoga::Display::Flex);
yogaStyle.setFlexDirection(yoga::FlexDirection::Row);
yogaStyle.setAlignItems(yoga::Align::Center);
yogaStyle.setJustifyContent(yoga::Justify::Center);
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.tag(2)
.surfaceId(surfaceId_)
.reference(nodeA_)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
listenToAllPointerEvents(props);
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDisplay(yoga::Display::Flex);
yogaStyle.setFlexDirection(yoga::FlexDirection::Column);
yogaStyle.setAlignItems(yoga::Align::FlexEnd);
yogaStyle.setJustifyContent(yoga::Justify::Center);
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(150));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(300));
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.tag(3)
.surfaceId(surfaceId_)
.reference(nodeAA_)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
listenToAllPointerEvents(props);
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
}),
Element<ViewShadowNode>()
.tag(4)
.surfaceId(surfaceId_)
.reference(nodeB_)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
listenToAllPointerEvents(props);
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDisplay(yoga::Display::Flex);
yogaStyle.setFlexDirection(yoga::FlexDirection::Column);
yogaStyle.setAlignItems(yoga::Align::FlexStart);
yogaStyle.setJustifyContent(yoga::Justify::Center);
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(150));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(300));
return sharedProps;
})
.children({
Element<ViewShadowNode>()
.tag(5)
.surfaceId(surfaceId_)
.reference(nodeBB_)
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
listenToAllPointerEvents(props);
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
})
})
.finalize([](RootShadowNode &shadowNode) {
shadowNode.layoutIfNeeded();
shadowNode.sealRecursive();
});
// clang-format on
// Build the node heirarchy
builder.build(elementRoot);
// Initialize shadow tree
auto layoutConstraints = LayoutConstraints{};
auto layoutContext = LayoutContext{};
auto shadowTree = std::make_unique<ShadowTree>(
surfaceId_,
layoutConstraints,
layoutContext,
*uiManager_,
*contextContainer);
shadowTree->commit(
[this](const RootShadowNode& /*oldRootShadowNode*/) {
return std::static_pointer_cast<RootShadowNode>(this->rootNode_);
},
{true});
// Start the surface in UIManager
uiManager_->startSurface(
std::move(shadowTree),
"test",
folly::dynamic::object,
DisplayMode::Visible);
}
void TearDown() override {
uiManager_->stopSurface(surfaceId_);
}
EventLog dispatchPointerEvent(
const std::shared_ptr<const ShadowNode>& target,
const std::string& eventName,
const PointerEvent& eventPayload) {
EventLog eventLog;
auto dispatchCallback = [&eventLog](
const ShadowNode& targetNode,
const std::string& type,
ReactEventPriority /*priority*/,
const EventPayload& eventPayload) {
eventLog.push_back({
.tag = targetNode.getTag(),
.eventName = type,
.payload = static_cast<const PointerEvent&>(eventPayload),
});
};
processor_.interceptPointerEvent(
target,
eventName,
ReactEventPriority::Default,
eventPayload,
dispatchCallback,
*uiManager_);
return eventLog;
}
SurfaceId surfaceId_{0};
std::shared_ptr<RootShadowNode> rootNode_;
std::shared_ptr<ViewShadowNode> nodeA_;
std::shared_ptr<ViewShadowNode> nodeAA_;
std::shared_ptr<ViewShadowNode> nodeB_;
std::shared_ptr<ViewShadowNode> nodeBB_;
PointerEventsProcessor processor_;
std::unique_ptr<UIManager> uiManager_;
std::unique_ptr<jsi::Runtime> runtime_;
};
TEST_F(PointerEventsProcessorTest, moveAcross) {
auto eventPayload = PointerEvent{};
eventPayload.pointerId = 1;
// First move event inside nodeAA
auto firstMoveLog =
dispatchPointerEvent(nodeAA_, "topPointerMove", eventPayload);
EXPECT_EQ(firstMoveLog.size(), 5);
EXPECT_EQ(firstMoveLog[0].tag, nodeAA_->getTag());
EXPECT_EQ(firstMoveLog[0].eventName, "topPointerOver");
EXPECT_EQ(firstMoveLog[1].tag, rootNode_->getTag());
EXPECT_EQ(firstMoveLog[1].eventName, "topPointerEnter");
EXPECT_EQ(firstMoveLog[2].tag, nodeA_->getTag());
EXPECT_EQ(firstMoveLog[2].eventName, "topPointerEnter");
EXPECT_EQ(firstMoveLog[3].tag, nodeAA_->getTag());
EXPECT_EQ(firstMoveLog[3].eventName, "topPointerEnter");
EXPECT_EQ(firstMoveLog[4].tag, nodeAA_->getTag());
EXPECT_EQ(firstMoveLog[4].eventName, "topPointerMove");
// Second move event inside nodeBB
auto secondMoveLog =
dispatchPointerEvent(nodeBB_, "topPointerMove", eventPayload);
EXPECT_EQ(secondMoveLog.size(), 7);
EXPECT_EQ(secondMoveLog[0].tag, nodeAA_->getTag());
EXPECT_EQ(secondMoveLog[0].eventName, "topPointerOut");
EXPECT_EQ(secondMoveLog[1].tag, nodeAA_->getTag());
EXPECT_EQ(secondMoveLog[1].eventName, "topPointerLeave");
EXPECT_EQ(secondMoveLog[2].tag, nodeA_->getTag());
EXPECT_EQ(secondMoveLog[2].eventName, "topPointerLeave");
EXPECT_EQ(secondMoveLog[3].tag, nodeBB_->getTag());
EXPECT_EQ(secondMoveLog[3].eventName, "topPointerOver");
EXPECT_EQ(secondMoveLog[4].tag, nodeB_->getTag());
EXPECT_EQ(secondMoveLog[4].eventName, "topPointerEnter");
EXPECT_EQ(secondMoveLog[5].tag, nodeBB_->getTag());
EXPECT_EQ(secondMoveLog[5].eventName, "topPointerEnter");
EXPECT_EQ(secondMoveLog[6].tag, nodeBB_->getTag());
EXPECT_EQ(secondMoveLog[6].eventName, "topPointerMove");
// Third move event also inside nodeBB (should be no derivative events
// emitted)
auto thirdMoveLog =
dispatchPointerEvent(nodeBB_, "topPointerMove", eventPayload);
EXPECT_EQ(thirdMoveLog.size(), 1);
EXPECT_EQ(thirdMoveLog[0].tag, nodeBB_->getTag());
EXPECT_EQ(thirdMoveLog[0].eventName, "topPointerMove");
// Last event emulates an event reporting that the pointer has left the root
// view
auto leavingMoveLog =
dispatchPointerEvent(rootNode_, "topPointerLeave", eventPayload);
EXPECT_EQ(leavingMoveLog.size(), 4);
EXPECT_EQ(leavingMoveLog[0].tag, nodeBB_->getTag());
EXPECT_EQ(leavingMoveLog[0].eventName, "topPointerOut");
EXPECT_EQ(leavingMoveLog[1].tag, nodeBB_->getTag());
EXPECT_EQ(leavingMoveLog[1].eventName, "topPointerLeave");
EXPECT_EQ(leavingMoveLog[2].tag, nodeB_->getTag());
EXPECT_EQ(leavingMoveLog[2].eventName, "topPointerLeave");
EXPECT_EQ(leavingMoveLog[3].tag, rootNode_->getTag());
EXPECT_EQ(leavingMoveLog[3].eventName, "topPointerLeave");
}
TEST_F(PointerEventsProcessorTest, directPress) {
auto eventPayload = PointerEvent{};
eventPayload.pointerId = 1;
// Emulate down event from the platform onto nodeA
auto downLog = dispatchPointerEvent(nodeA_, "topPointerDown", eventPayload);
EXPECT_EQ(downLog.size(), 4);
EXPECT_EQ(downLog[0].tag, nodeA_->getTag());
EXPECT_EQ(downLog[0].eventName, "topPointerOver");
EXPECT_EQ(downLog[1].tag, rootNode_->getTag());
EXPECT_EQ(downLog[1].eventName, "topPointerEnter");
EXPECT_EQ(downLog[2].tag, nodeA_->getTag());
EXPECT_EQ(downLog[2].eventName, "topPointerEnter");
EXPECT_EQ(downLog[3].tag, nodeA_->getTag());
EXPECT_EQ(downLog[3].eventName, "topPointerDown");
// Emulate an up event on nodeA
auto upLog = dispatchPointerEvent(nodeA_, "topPointerUp", eventPayload);
EXPECT_EQ(upLog.size(), 4);
EXPECT_EQ(upLog[0].tag, nodeA_->getTag());
EXPECT_EQ(upLog[0].eventName, "topPointerUp");
EXPECT_EQ(upLog[1].tag, nodeA_->getTag());
EXPECT_EQ(upLog[1].eventName, "topPointerOut");
EXPECT_EQ(upLog[2].tag, nodeA_->getTag());
EXPECT_EQ(upLog[2].eventName, "topPointerLeave");
EXPECT_EQ(upLog[3].tag, rootNode_->getTag());
EXPECT_EQ(upLog[3].eventName, "topPointerLeave");
}
TEST_F(PointerEventsProcessorTest, indirectPress) {
auto eventPayload = PointerEvent{};
eventPayload.pointerId = 1;
// Emulate a move event before the press event sequence
auto moveLog = dispatchPointerEvent(nodeA_, "topPointerMove", eventPayload);
EXPECT_EQ(moveLog.size(), 4);
EXPECT_EQ(moveLog[0].tag, nodeA_->getTag());
EXPECT_EQ(moveLog[0].eventName, "topPointerOver");
EXPECT_EQ(moveLog[1].tag, rootNode_->getTag());
EXPECT_EQ(moveLog[1].eventName, "topPointerEnter");
EXPECT_EQ(moveLog[2].tag, nodeA_->getTag());
EXPECT_EQ(moveLog[2].eventName, "topPointerEnter");
EXPECT_EQ(moveLog[3].tag, nodeA_->getTag());
EXPECT_EQ(moveLog[3].eventName, "topPointerMove");
// Emulate a down event from the platform onto nodeA
auto downLog = dispatchPointerEvent(nodeA_, "topPointerDown", eventPayload);
EXPECT_EQ(downLog.size(), 1);
EXPECT_EQ(downLog[0].tag, nodeA_->getTag());
EXPECT_EQ(downLog[0].eventName, "topPointerDown");
// Emulate an up event on nodeA
auto upLog = dispatchPointerEvent(nodeA_, "topPointerUp", eventPayload);
EXPECT_EQ(upLog.size(), 1);
EXPECT_EQ(upLog[0].tag, nodeA_->getTag());
EXPECT_EQ(upLog[0].eventName, "topPointerUp");
}
} // namespace facebook::react