Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ ShadowNode::ShadowNode(
react_native_assert(children_);
traits_.set(ShadowNodeTraits::Trait::ChildrenAreShared);

// A clone that keeps the source's props keeps the native props they include.
if (fragment.props == ShadowNodeFragment::propsPlaceholder()) {
nativePropsVersion_ = sourceShadowNode.nativePropsVersion_;
}

if (fragment.children) {
for (const auto& child : *children_) {
child->family_->setParent(family_);
Expand All @@ -140,11 +145,17 @@ std::shared_ptr<ShadowNode> ShadowNode::clone(
const auto& componentDescriptor = family.componentDescriptor_;

std::optional<RawProps> propsOverride;
uint32_t nativePropsVersion = 0;
{
std::lock_guard<std::mutex> lock(family.nativePropsMutex);
if (family.nativeProps_DEPRECATED != nullptr) {
if (fragment.props == ShadowNodeFragment::propsPlaceholder()) {
propsOverride.emplace(*family.nativeProps_DEPRECATED);
// Props of a node that already include the current
// `nativeProps_DEPRECATED` don't need them applied again.
if (nativePropsVersion_ != family.nativePropsVersion_DEPRECATED) {
propsOverride.emplace(*family.nativeProps_DEPRECATED);
nativePropsVersion = family.nativePropsVersion_DEPRECATED;
}
} else {
// TODO: We might need to merge fragment.props with
// `family.nativeProps_DEPRECATED`.
Expand All @@ -158,12 +169,14 @@ std::shared_ptr<ShadowNode> ShadowNode::clone(
// result escapes the lock so the rest of the clone work runs
// unblocked.
auto propsParserContext = PropsParserContext{family_->getSurfaceId(), {}};
return componentDescriptor.cloneShadowNode(
auto clonedShadowNode = componentDescriptor.cloneShadowNode(
*this,
{.props = componentDescriptor.cloneProps(
propsParserContext, props_, std::move(*propsOverride)),
.children = fragment.children,
.state = fragment.state});
clonedShadowNode->nativePropsVersion_ = nativePropsVersion;
return clonedShadowNode;
}

return componentDescriptor.cloneShadowNode(*this, fragment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ class ShadowNode : public Sealable, public DebugStringConvertible, public jsi::N
*/
mutable std::atomic<bool> hasBeenMounted_{false};

/*
* The family's `nativePropsVersion_DEPRECATED` that `props_` already
* include, so that cloning doesn't apply `nativeProps_DEPRECATED` again.
*/
uint32_t nativePropsVersion_{0};

static Props::Shared propsForClonedShadowNode(const ShadowNode &sourceShadowNode, const Props::Shared &props);

protected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ class ShadowNodeFamily final : public jsi::NativeState {
mutable std::unique_ptr<folly::dynamic> nativeProps_DEPRECATED;
mutable std::mutex nativePropsMutex;

/*
* Incremented whenever `nativeProps_DEPRECATED` changes, so a cloned node
* can tell whether its props already include them.
*/
mutable uint32_t nativePropsVersion_DEPRECATED{0};

/**
* @return tag for the ShadowNodeFamily.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ std::shared_ptr<ShadowNode> UIManager::cloneNode(
*family.nativeProps_DEPRECATED, // source
(folly::dynamic)rawProps, // patch
NullValueStrategy::Ignore));
family.nativePropsVersion_DEPRECATED++;

// 2. Compute the final set of props.
//
Expand Down Expand Up @@ -465,6 +466,7 @@ void UIManager::setNativeProps_DEPRECATED(
auto& family = shadowNode->getFamily();
{
std::lock_guard<std::mutex> lock(family.nativePropsMutex);
family.nativePropsVersion_DEPRECATED++;
if (family.nativeProps_DEPRECATED) {
// Values in `rawProps` patch (take precedence over)
// `nativeProps_DEPRECATED`. For example, if both
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,45 @@ TEST_F(FabricUIManagerTest, SetNativePropsMergesWithReactRender) {
EXPECT_FLOAT_EQ(merged.opacity, 0.5);
}

// Cloning a node whose props already include the props set by
// `setNativeProps_DEPRECATED` (as Yoga does for every child of a node it lays
// out) keeps those props instead of parsing them again. Props set later are
// still applied.
TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsOnlyOnce) {
uiManager_->setNativeProps_DEPRECATED(
currentViewNode(), RawProps(folly::dynamic::object("opacity", 0.5)));

auto first = currentViewNode()->clone({});
EXPECT_FLOAT_EQ(
static_cast<const ViewProps&>(*first->getProps()).opacity, 0.5);

auto second = first->clone({});
EXPECT_EQ(second->getProps(), first->getProps());

uiManager_->setNativeProps_DEPRECATED(
currentViewNode(), RawProps(folly::dynamic::object("opacity", 0.25)));
auto third = second->clone({});
EXPECT_NE(third->getProps(), second->getProps());
EXPECT_FLOAT_EQ(
static_cast<const ViewProps&>(*third->getProps()).opacity, 0.25);
}

// After a React re-render replaces a node's props, the next clone applies the
// props set by `setNativeProps_DEPRECATED` again.
TEST_F(FabricUIManagerTest, CloneAppliesSetNativePropsAfterReactRender) {
uiManager_->setNativeProps_DEPRECATED(
currentViewNode(), RawProps(folly::dynamic::object("opacity", 0.5)));
auto applied = currentViewNode()->clone({});

auto rerendered = uiManager_->cloneNode(
*applied,
/*children=*/nullptr,
RawProps(folly::dynamic::object("nativeID", "from-react")));
auto cloned = rerendered->clone({});
EXPECT_NE(cloned->getProps(), rerendered->getProps());
auto& props = static_cast<const ViewProps&>(*cloned->getProps());
EXPECT_EQ(props.nativeId, "from-react");
EXPECT_FLOAT_EQ(props.opacity, 0.5);
}

} // namespace facebook::react
Loading