Skip to content

Add Fantom test for layout props & fix an issue in c++ animated #52110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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 @@ -17,6 +17,7 @@ import ensureInstance from '../../../src/private/__tests__/utilities/ensureInsta
import * as Fantom from '@react-native/fantom';
import {createRef} from 'react';
import {Animated, View, useAnimatedValue} from 'react-native';
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';

Expand Down Expand Up @@ -152,6 +153,7 @@ test('animation driven by onScroll event', () => {

test('animated opacity', () => {
let _opacity;
let _opacityAnimation;
const viewRef = createRef<HostInstance>();

function MyApp() {
Expand Down Expand Up @@ -182,7 +184,7 @@ test('animated opacity', () => {
expect(viewElement.getBoundingClientRect().x).toBe(0);

Fantom.runTask(() => {
Animated.timing(_opacity, {
_opacityAnimation = Animated.timing(_opacity, {
toValue: 0,
duration: 30,
useNativeDriver: true,
Expand All @@ -196,6 +198,10 @@ test('animated opacity', () => {

// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
Fantom.runWorkLoop();
// TODO: this shouldn't be neccessary since animation should be stopped after duration
Fantom.runTask(() => {
_opacityAnimation?.stop();
});

expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
<rn-view opacity="0" />,
Expand Down Expand Up @@ -475,3 +481,61 @@ describe('Value.extractOffset', () => {
Fantom.runWorkLoop();
});
});

test('animate layout props', () => {
const viewRef = createRef<HostInstance>();
allowStyleProp('height');

let _animatedHeight;
let _heightAnimation;

function MyApp() {
const animatedHeight = useAnimatedValue(0);
_animatedHeight = animatedHeight;
return (
<Animated.View
ref={viewRef}
style={[
{
width: 100,
height: animatedHeight,
},
]}
/>
);
}

const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(<MyApp />);
});

const viewElement = ensureInstance(viewRef.current, ReactNativeElement);

Fantom.runTask(() => {
_heightAnimation = Animated.timing(_animatedHeight, {
toValue: 100,
duration: 10,
useNativeDriver: true,
}).start();
});

Fantom.unstable_produceFramesForDuration(10);

// TODO: this shouldn't be neccessary since animation should be stopped after duration
Fantom.runTask(() => {
_heightAnimation?.stop();
});

// $FlowFixMe[incompatible-use]
expect(Fantom.unstable_getDirectManipulationProps(viewElement).height).toBe(
100,
);

expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100);

expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
<rn-view height="100.000000" />,
);
});
6 changes: 6 additions & 0 deletions packages/react-native/React/Fabric/RCTScheduler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag,
[scheduler.delegate schedulerDidSynchronouslyUpdateViewOnUIThread:tag props:props];
}

void schedulerDidUpdateShadowTree(const std::unordered_map<Tag, folly::dynamic> &tagToProps) override
{
// Does nothing.
// This delegate method is not currently used on iOS.
}

private:
void *scheduler_;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,11 @@ void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateViewOnUIThread(
}
}

void FabricUIManagerBinding::schedulerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& /*tagToProps*/) {
// no-op
}

void FabricUIManagerBinding::onAnimationStarted() {
auto mountingManager = getMountingManager("onAnimationStarted");
if (!mountingManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,
Tag tag,
const folly::dynamic& props) override;

void schedulerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) override;

void setPixelDensity(float pointScaleFactor);

void driveCxxAnimations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ void Scheduler::uiManagerShouldSynchronouslyUpdateViewOnUIThread(
}
}

void Scheduler::uiManagerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) {
if (delegate_ != nullptr) {
delegate_->schedulerDidUpdateShadowTree(tagToProps);
}
}

void Scheduler::uiManagerShouldAddEventListener(
std::shared_ptr<const EventListener> listener) {
addEventListener(listener);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class Scheduler final : public UIManagerDelegate {
void uiManagerShouldSynchronouslyUpdateViewOnUIThread(
Tag tag,
const folly::dynamic& props) override;
void uiManagerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) override;
void uiManagerShouldAddEventListener(
std::shared_ptr<const EventListener> listener) final;
void uiManagerShouldRemoveEventListener(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class SchedulerDelegate {
Tag tag,
const folly::dynamic& props) = 0;

virtual void schedulerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) = 0;

virtual ~SchedulerDelegate() noexcept = default;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ class UIManagerDelegate {
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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ void UIManager::updateShadowTree(
LOG(ERROR) << "Root ShadowNode has not been cloned";
}
});

if (delegate_ != nullptr) {
delegate_->uiManagerDidUpdateShadowTree(tagToProps);
}
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include "PropsAnimatedNode.h"

#include <react/debug/react_native_assert.h>
#include <react/renderer/animated/NativeAnimatedAllowlist.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/StyleAnimatedNode.h>
Expand All @@ -31,12 +30,8 @@ bool isLayoutStyleUpdated(
if (node->type() == AnimatedNodeType::Style) {
if (const auto& styleNode =
manager.getAnimatedNode<StyleAnimatedNode>(nodeTag)) {
auto& styleNodeProps = styleNode->getProps();
for (const auto& styleNodeProp : styleNodeProps.items()) {
if (getDirectManipulationAllowlist().count(
styleNodeProp.first.asString()) == 0u) {
return true;
}
if (styleNode->isLayoutStyleUpdated()) {
return true;
}
}
}
Expand All @@ -53,9 +48,7 @@ PropsAnimatedNode::PropsAnimatedNode(
const folly::dynamic& config,
NativeAnimatedNodesManager& manager)
: AnimatedNode(tag, config, manager, AnimatedNodeType::Props),
props_(folly::dynamic::object()),
layoutStyleUpdated_(isLayoutStyleUpdated(getConfig()["props"], manager)) {
}
props_(folly::dynamic::object()) {}

void PropsAnimatedNode::connectToView(Tag viewTag) {
react_native_assert(
Expand Down Expand Up @@ -145,6 +138,8 @@ void PropsAnimatedNode::update(bool forceFabricCommit) {
}
}

layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);

manager_->schedulePropsCommit(
connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class PropsAnimatedNode final : public AnimatedNode {
private:
std::mutex propsMutex_;
folly::dynamic props_;
const bool layoutStyleUpdated_;
bool layoutStyleUpdated_{false};

Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,29 @@

#include "StyleAnimatedNode.h"

#include <react/renderer/animated/NativeAnimatedAllowlist.h>
#include <react/renderer/animated/NativeAnimatedNodesManager.h>
#include <react/renderer/animated/nodes/ColorAnimatedNode.h>
#include <react/renderer/animated/nodes/TransformAnimatedNode.h>
#include <react/renderer/animated/nodes/ValueAnimatedNode.h>

namespace facebook::react {

namespace {

bool isLayoutPropsUpdated(const folly::dynamic& props) {
for (const auto& styleNodeProp : props.items()) {
if (getDirectManipulationAllowlist().count(
styleNodeProp.first.asString()) == 0u) {
return true;
}
}

return false;
}

} // namespace

StyleAnimatedNode::StyleAnimatedNode(
Tag tag,
const folly::dynamic& config,
Expand Down Expand Up @@ -77,5 +94,8 @@ void StyleAnimatedNode::update() {
}
}
}

layoutStyleUpdated_ = isLayoutPropsUpdated(props_);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ class StyleAnimatedNode final : public AnimatedNode {
return props_;
}

bool isLayoutStyleUpdated() const noexcept {
return layoutStyleUpdated_;
}

private:
folly::dynamic props_;
bool layoutStyleUpdated_;
};
} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ void SchedulerDelegateImpl::schedulerShouldSynchronouslyUpdateViewOnUIThread(
mountingManager_->synchronouslyUpdateViewOnUIThread(tag, props);
}

void SchedulerDelegateImpl::schedulerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) {
mountingManager_->onUpdateShadowTree(tagToProps);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class SchedulerDelegateImpl : public SchedulerDelegate {
Tag tag,
const folly::dynamic& props) override;

void schedulerDidUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) override;

std::shared_ptr<IMountingManager> mountingManager_;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class IMountingManager {
Tag reactTag,
const folly::dynamic& changedProps) {};

virtual void onUpdateShadowTree(
const std::unordered_map<Tag, folly::dynamic>& tagToProps) {};

virtual void initializeAccessibilityManager() {};

virtual void setAccessibilityFocusedView(Tag viewTag) {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ interface Spec extends TurboModule {
) => $ReadOnly<{
[string]: mixed,
}>;
getFabricUpdateProps: (shadowNode: mixed /* ShadowNode */) => $ReadOnly<{
[string]: mixed,
}>;
flushMessageQueue: () => void;
flushEventQueue: () => void;
produceFramesForDuration: (miliseconds: number) => void;
Expand Down
7 changes: 7 additions & 0 deletions private/react-native-fantom/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ export function unstable_getDirectManipulationProps(
return NativeFantom.getDirectManipulationProps(shadowNode);
}

export function unstable_getFabricUpdateProps(node: ReadOnlyNode): $ReadOnly<{
[string]: mixed,
}> {
const shadowNode = getNativeNodeReference(node);
return NativeFantom.getFabricUpdateProps(shadowNode);
}

/**
* Simulates running a task on the UI thread and forces side effect to drain
* the event queue, scheduling events to be dispatched to JavaScript.
Expand Down
Loading