diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index bd327efc351234..01b25a86aac145 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -668,9 +668,14 @@ void YogaLayoutableShadowNode::layoutTree( layoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor; layoutMetrics.wasLeftAndRightSwapped = swapLeftAndRight; setLayoutMetrics(layoutMetrics); + if (layoutContext.includeOriginFromRoot) { + setOriginFromRoot(layoutMetrics.frame.origin); + } yogaNode_.setHasNewLayout(false); } + layoutContext.rootNode = this; + layout(layoutContext); } @@ -728,6 +733,15 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) { childNode.setLayoutMetrics(newLayoutMetrics); + if (layoutContext.includeOriginFromRoot) { + childNode.setOriginFromRoot( + LayoutableShadowNode::computeOriginFromRoot( + originFromRoot_, + newLayoutMetrics.frame, + childNode.getTransform(), + childNode.getContentOriginOffset(true))); + } + if (newLayoutMetrics.displayType != DisplayType::None) { childNode.layout(layoutContext); } diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h index c71d447be3773c..cfe5a306ddfd83 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h @@ -59,6 +59,19 @@ struct LayoutContext { * If React Native takes up entire screen, it will be {0, 0}. */ Point viewportOffset{}; + + /* + * Flag indicating whether to calculate and store originFromRoot + * for each LayoutableShadowNode during layout. This is typically + * enabled when laying out children of ViewTransitionViewShadowNode. + */ + bool includeOriginFromRoot{false}; + + /* + * Pointer to the root node of the layout tree. Set during layoutTree() + * and used by nodes that need to compute their position relative to root. + */ + const LayoutableShadowNode *rootNode{nullptr}; }; inline bool operator==(const LayoutContext &lhs, const LayoutContext &rhs) @@ -68,13 +81,17 @@ inline bool operator==(const LayoutContext &lhs, const LayoutContext &rhs) lhs.affectedNodes, lhs.swapLeftAndRightInRTL, lhs.fontSizeMultiplier, - lhs.viewportOffset) == + lhs.viewportOffset, + lhs.includeOriginFromRoot, + lhs.rootNode) == std::tie( rhs.pointScaleFactor, rhs.affectedNodes, rhs.swapLeftAndRightInRTL, rhs.fontSizeMultiplier, - rhs.viewportOffset); + rhs.viewportOffset, + rhs.includeOriginFromRoot, + rhs.rootNode); } inline bool operator!=(const LayoutContext &lhs, const LayoutContext &rhs) diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h index 8be251aabf58a2..8007c5ca1687ac 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace facebook::react { @@ -98,6 +99,16 @@ struct LayoutMetrics { static const LayoutMetrics EmptyLayoutMetrics = { /* .frame = */ .frame = {.origin = {.x = 0, .y = 0}, .size = {.width = -1.0, .height = -1.0}}}; +/* + * Represents some undefined, not-yet-computed or meaningless value of + * `originFromRoot` (Point type). + * Used to indicate that originFromRoot has not been calculated for a node. + * The value uses negative infinity to distinguish from valid coordinates. + */ +static const Point EmptyOriginFromRoot = { + .x = -std::numeric_limits::infinity(), + .y = -std::numeric_limits::infinity()}; + #if RN_DEBUG_STRING_CONVERTIBLE std::string getDebugName(const LayoutMetrics &object); diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp index 515ee47a39f28f..24bb06215ffbb1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp @@ -31,7 +31,10 @@ LayoutableShadowNode::LayoutableShadowNode( : ShadowNode(sourceShadowNode, fragment), layoutMetrics_( static_cast(sourceShadowNode) - .layoutMetrics_) {} + .layoutMetrics_), + originFromRoot_( + static_cast(sourceShadowNode) + .originFromRoot_) {} LayoutMetrics LayoutableShadowNode::computeLayoutMetricsFromRoot( const ShadowNodeFamily& descendantNodeFamily, @@ -197,10 +200,34 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( return layoutMetrics; } +Point LayoutableShadowNode::computeOriginFromRoot( + Point parentOriginFromRoot, + const Rect& frame, + const Transform& transform, + Point contentOriginOffset) { + if (parentOriginFromRoot == EmptyOriginFromRoot) { + return EmptyOriginFromRoot; + } + + Point resultOrigin = frame.origin; + + if (transform != Transform::Identity()) { + resultOrigin = transform.applyWithCenter(frame, frame.getCenter()).origin; + } + + resultOrigin += parentOriginFromRoot + contentOriginOffset; + + return resultOrigin; +} + LayoutMetrics LayoutableShadowNode::getLayoutMetrics() const { return layoutMetrics_; } +Point LayoutableShadowNode::getOriginFromRoot() const { + return originFromRoot_; +} + void LayoutableShadowNode::setLayoutMetrics(LayoutMetrics layoutMetrics) { ensureUnsealed(); @@ -211,6 +238,16 @@ void LayoutableShadowNode::setLayoutMetrics(LayoutMetrics layoutMetrics) { layoutMetrics_ = layoutMetrics; } +void LayoutableShadowNode::setOriginFromRoot(Point originFromRoot) { + ensureUnsealed(); + + if (originFromRoot_ == originFromRoot) { + return; + } + + originFromRoot_ = originFromRoot; +} + Transform LayoutableShadowNode::getTransform() const { return Transform::Identity(); } diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.h index e983dce2ad31f6..783b3929fe5d18 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.h @@ -71,6 +71,18 @@ class LayoutableShadowNode : public ShadowNode { */ static LayoutMetrics computeRelativeLayoutMetrics(const AncestorList &ancestors, LayoutInspectingPolicy policy); + /* + * Computes the origin from root for a child node given the parent's origin + * from root, the child's layout metrics, transform, and content origin offset. + * This is similar to how computeRelativeLayoutMetrics computes positions, but + * avoids traversing all the ancestors. + */ + static Point computeOriginFromRoot( + Point parentOriginFromRoot, + const Rect &frame, + const Transform &transform, + Point contentOriginOffset); + /* * Performs layout of the tree starting from this node. Usually is being * called on the root node. @@ -110,6 +122,8 @@ class LayoutableShadowNode : public ShadowNode { */ LayoutMetrics getLayoutMetrics() const; + Point getOriginFromRoot() const; + /* * Returns a transform object that represents transformations that will/should * be applied on top of regular layout metrics by mounting layer. @@ -133,6 +147,8 @@ class LayoutableShadowNode : public ShadowNode { */ void setLayoutMetrics(LayoutMetrics layoutMetrics); + void setOriginFromRoot(Point originFromRoot); + /* * Returns the ShadowNode that is rendered at the Point received as a * parameter. @@ -167,6 +183,8 @@ class LayoutableShadowNode : public ShadowNode { #endif LayoutMetrics layoutMetrics_; + + Point originFromRoot_{EmptyOriginFromRoot}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp index 575221be7a332a..f2345d367c6d50 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp @@ -1431,4 +1431,162 @@ TEST(LayoutableShadowNodeTest, inversedContentOriginOffset) { EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 130); } +/* + * Tests for computeOriginFromRoot utility function. + * These tests cross-validate computeOriginFromRoot against + * computeRelativeLayoutMetrics to ensure they produce consistent results. + */ +TEST(LayoutableShadowNodeTest, computeOriginFromRoot) { + auto builder = simpleComponentBuilder(); + auto childShadowNode = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {.x=100, .y=200}; + layoutMetrics.frame.size = {.width=500, .height=500}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {.x=10, .y=20}; + layoutMetrics.frame.size = {.width=100, .height=200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .reference(childShadowNode) + }); + // clang-format on + + auto parentShadowNode = builder.build(element); + + // Get expected origin from computeRelativeLayoutMetrics + auto relativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode->getFamily(), *parentShadowNode, {}); + + // Compute origin using computeOriginFromRoot + // Parent's origin from root is {0, 0} since parent is the root + Point parentOriginFromRoot = {.x = 0, .y = 0}; + auto computedOrigin = LayoutableShadowNode::computeOriginFromRoot( + parentOriginFromRoot, + childShadowNode->getLayoutMetrics().frame, + childShadowNode->getTransform(), + childShadowNode->getContentOriginOffset(true)); + + EXPECT_EQ(computedOrigin.x, relativeLayoutMetrics.frame.origin.x); + EXPECT_EQ(computedOrigin.y, relativeLayoutMetrics.frame.origin.y); +} + +TEST(LayoutableShadowNodeTest, computeOriginFromRootWithTransform) { + auto builder = simpleComponentBuilder(); + auto childShadowNode = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {.width=1000, .height=1000}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {.x=10, .y=20}; + layoutMetrics.frame.size = {.width=100, .height=200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .props([] { + auto sharedProps = std::make_shared(); + sharedProps->transform = Transform::Scale(0.5, 0.5, 1); + return sharedProps; + }) + .reference(childShadowNode) + }); + // clang-format on + + auto parentShadowNode = builder.build(element); + + // Get expected origin from computeRelativeLayoutMetrics + auto relativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode->getFamily(), *parentShadowNode, {}); + + // Compute origin using computeOriginFromRoot + Point parentOriginFromRoot = {.x = 0, .y = 0}; + auto computedOrigin = LayoutableShadowNode::computeOriginFromRoot( + parentOriginFromRoot, + childShadowNode->getLayoutMetrics().frame, + childShadowNode->getTransform(), + childShadowNode->getContentOriginOffset(true)); + + EXPECT_EQ(computedOrigin.x, relativeLayoutMetrics.frame.origin.x); + EXPECT_EQ(computedOrigin.y, relativeLayoutMetrics.frame.origin.y); +} + +TEST(LayoutableShadowNodeTest, computeOriginFromRootNested) { + auto builder = simpleComponentBuilder(); + auto parentShadowNode = std::shared_ptr{}; + auto childShadowNode = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {.x=0, .y=0}; + layoutMetrics.frame.size = {.width=1000, .height=1000}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {.x=50, .y=50}; + layoutMetrics.frame.size = {.width=200, .height=200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .reference(parentShadowNode) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {.x=10, .y=20}; + layoutMetrics.frame.size = {.width=100, .height=100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .reference(childShadowNode) + }) + }); + // clang-format on + + auto rootShadowNode = builder.build(element); + + // Get expected origin from computeRelativeLayoutMetrics + auto relativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode->getFamily(), *rootShadowNode, {}); + + // Compute origin using computeOriginFromRoot + // First compute parent's origin from root + Point rootOrigin = {.x = 0, .y = 0}; + auto parentOriginFromRoot = LayoutableShadowNode::computeOriginFromRoot( + rootOrigin, + parentShadowNode->getLayoutMetrics().frame, + parentShadowNode->getTransform(), + parentShadowNode->getContentOriginOffset(true)); + + // Then compute child's origin from root using parent's origin + auto computedOrigin = LayoutableShadowNode::computeOriginFromRoot( + parentOriginFromRoot, + childShadowNode->getLayoutMetrics().frame, + childShadowNode->getTransform(), + childShadowNode->getContentOriginOffset(true)); + + EXPECT_EQ(computedOrigin.x, relativeLayoutMetrics.frame.origin.x); + EXPECT_EQ(computedOrigin.y, relativeLayoutMetrics.frame.origin.y); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index 58c5bbaa4e0844..877bf14d6fa07a 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -233,7 +233,10 @@ void ShadowTree::setCommitMode(CommitMode commitMode) const { // initial revision never contains any commits so mounting it here is // incorrect if (commitMode == CommitMode::Normal && revision.number != INITIAL_REVISION) { - mount(revision, true); + mount( + revision, + /*mountSynchronously*/ true, + /*source */ CommitSource::Unknown); } } @@ -370,7 +373,10 @@ CommitStatus ShadowTree::tryCommit( emitLayoutEvents(affectedLayoutableNodes); if (commitMode == CommitMode::Normal) { - mount(std::move(newRevision), commitOptions.mountSynchronously); + mount( + std::move(newRevision), + commitOptions.mountSynchronously, + commitOptions.source); } return CommitStatus::Succeeded; @@ -381,11 +387,13 @@ ShadowTreeRevision ShadowTree::getCurrentRevision() const { return currentRevision_; } -void ShadowTree::mount(ShadowTreeRevision revision, bool mountSynchronously) - const { +void ShadowTree::mount( + ShadowTreeRevision revision, + bool mountSynchronously, + ShadowTreeCommitSource source) const { mountingCoordinator_->push(std::move(revision)); delegate_.shadowTreeDidFinishTransaction( - mountingCoordinator_, mountSynchronously); + mountingCoordinator_, mountSynchronously, source); } void ShadowTree::commitEmptyTree() const { @@ -423,7 +431,10 @@ void ShadowTree::emitLayoutEvents( } void ShadowTree::notifyDelegatesOfUpdates() const { - delegate_.shadowTreeDidFinishTransaction(mountingCoordinator_, true); + delegate_.shadowTreeDidFinishTransaction( + mountingCoordinator_, + /*mountSynchronously*/ true, + /*source */ CommitSource::Unknown); } inline ShadowTree::UniqueLock ShadowTree::uniqueCommitLock() const { diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h index 3b44d6e49ca470..345cdc5e0ef74b 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -139,7 +139,7 @@ class ShadowTree final { private: constexpr static ShadowTreeRevision::Number INITIAL_REVISION{0}; - void mount(ShadowTreeRevision revision, bool mountSynchronously) const; + void mount(ShadowTreeRevision revision, bool mountSynchronously, ShadowTreeCommitSource source) const; void emitLayoutEvents(std::vector &affectedLayoutableNodes) const; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h index 650d898a6f3453..d33e1eedc43704 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h @@ -13,6 +13,7 @@ namespace facebook::react { class ShadowTree; struct ShadowTreeCommitOptions; +enum class ShadowTreeCommitSource; /* * Abstract class for ShadowTree's delegate. @@ -36,7 +37,8 @@ class ShadowTreeDelegate { */ virtual void shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, - bool mountSynchronously) const = 0; + bool mountSynchronously, + ShadowTreeCommitSource source) const = 0; virtual ~ShadowTreeDelegate() noexcept = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.cpp index 0bd089b6943960..b68ace27e9d8be 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.cpp @@ -20,6 +20,14 @@ static LayoutMetrics layoutMetricsFromShadowNode(const ShadowNode& shadowNode) { : EmptyLayoutMetrics; } +static Point originFromRootFromShadowNode(const ShadowNode& shadowNode) { + auto layoutableShadowNode = + dynamic_cast(&shadowNode); + return layoutableShadowNode != nullptr + ? layoutableShadowNode->getOriginFromRoot() + : EmptyOriginFromRoot; +} + ShadowView::ShadowView(const ShadowNode& shadowNode) : componentName(shadowNode.getComponentName()), componentHandle(shadowNode.getComponentHandle()), @@ -29,6 +37,7 @@ ShadowView::ShadowView(const ShadowNode& shadowNode) props(shadowNode.getProps()), eventEmitter(shadowNode.getEventEmitter()), layoutMetrics(layoutMetricsFromShadowNode(shadowNode)), + originFromRoot(originFromRootFromShadowNode(shadowNode)), state(shadowNode.getState()) {} bool ShadowView::operator==(const ShadowView& rhs) const { @@ -39,6 +48,7 @@ bool ShadowView::operator==(const ShadowView& rhs) const { this->props, this->eventEmitter, this->layoutMetrics, + this->originFromRoot, this->state) == std::tie( rhs.surfaceId, @@ -47,6 +57,7 @@ bool ShadowView::operator==(const ShadowView& rhs) const { rhs.props, rhs.eventEmitter, rhs.layoutMetrics, + rhs.originFromRoot, rhs.state); } diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.h index b78b4cfeca1e2b..da852bcf810f0e 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.h @@ -45,6 +45,7 @@ struct ShadowView final { Props::Shared props{}; EventEmitter::Shared eventEmitter{}; LayoutMetrics layoutMetrics{EmptyLayoutMetrics}; + Point originFromRoot{}; State::Shared state{}; }; @@ -73,6 +74,7 @@ struct hash { shadowView.props, shadowView.eventEmitter, shadowView.layoutMetrics, + shadowView.originFromRoot, shadowView.state); } }; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 931bfce1c1e10a..edde2f96595901 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -636,13 +636,21 @@ RootShadowNode::Unshared UIManager::shadowTreeWillCommit( void UIManager::shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, - bool mountSynchronously) const { + bool mountSynchronously, + ShadowTreeCommitSource source) const { TraceSection s("UIManager::shadowTreeDidFinishTransaction"); + const auto surfaceId = mountingCoordinator->getSurfaceId(); if (delegate_ != nullptr) { delegate_->uiManagerDidFinishTransaction( std::move(mountingCoordinator), mountSynchronously); } + + std::shared_lock lock(commitHookMutex_); + + for (auto* commitHook : commitHooks_) { + commitHook->shadowTreeDidFinishTransaction(surfaceId, source); + } } void UIManager::reportMount(SurfaceId surfaceId) const { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 794a5559a553fe..25c663fb1a0670 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -127,7 +127,8 @@ class UIManager final : public ShadowTreeDelegate { void shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, - bool mountSynchronously) const override; + bool mountSynchronously, + ShadowTreeCommitSource source) const override; RootShadowNode::Unshared shadowTreeWillCommit( const ShadowTree &shadowTree, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h index 8c34b59ac03fee..a20651b27c6111 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h @@ -13,6 +13,7 @@ namespace facebook::react { class ShadowTree; struct ShadowTreeCommitOptions; +enum class ShadowTreeCommitSource; class UIManager; /* @@ -54,6 +55,8 @@ class UIManagerCommitHook { return newRootShadowNode; } + virtual void shadowTreeDidFinishTransaction(SurfaceId surfaceId, ShadowTreeCommitSource source) const noexcept {} + virtual ~UIManagerCommitHook() noexcept = default; };