Skip to content

Commit afb5436

Browse files
sammy-SCfacebook-github-bot
authored andcommitted
introduce a way to inspect shadow node revision in Fantom (#51566)
Summary: Pull Request resolved: #51566 changelog: [internal] Expose [revision](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h#L229) of shadow node in Fantom tests. This makes it possible to write tests verifying that shadow nodes are only cloned when they should. Even though excessive cloning does not usually lead to bugs, it may lead to performance problems. Also introduce a test showing a performance problem where changing height of "Sibling" view from 1 to 2 will lead to component `D` being cloned by Yoga. Component D is not affected by the size change of Sibling and the clone is unnecessary. ```jsx <ScrollView> <View id="Sibling" style={{ height: 1 }} /> <View id="A"> <View id="B"> <View id="C"> <View id="D" ref={ref} /> </View> </View> </View> </ScrollView> ``` Reviewed By: rshest Differential Revision: D75287261 fbshipit-source-id: ea5acb2f5d7ba6e1e5bf895d8f82a16471122ec5
1 parent ca5f4d1 commit afb5436

4 files changed

Lines changed: 139 additions & 0 deletions

File tree

packages/react-native-fantom/src/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,19 @@ export function createShadowNodeReferenceCounter(
616616
return NativeFantom.createShadowNodeReferenceCounter(shadowNode);
617617
}
618618

619+
/**
620+
* Returns a function that returns the current revision number for the supplied
621+
* element's shadow node.
622+
*
623+
* @param node The node for which to create a revision getter.
624+
*/
625+
export function createShadowNodeRevisionGetter(
626+
node: ReactNativeElement,
627+
): () => ?number {
628+
let shadowNode = getNativeNodeReference(node);
629+
return NativeFantom.createShadowNodeRevisionGetter(shadowNode);
630+
}
631+
619632
/**
620633
* Saves a heap snapshot after forcing garbage collection.
621634
*
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import ReactNativeElement from '../../webapis/dom/nodes/ReadOnlyNode';
12+
import ensureInstance from './ensureInstance';
13+
import * as Fantom from '@react-native/fantom';
14+
15+
function createShadowNodeRevisionGetter(
16+
element: ReactNativeElement,
17+
): () => ?number {
18+
const getRevision = Fantom.createShadowNodeRevisionGetter(element);
19+
return () => {
20+
return getRevision();
21+
};
22+
}
23+
24+
export function createShadowNodeReferenceGetterRef(): [
25+
() => ?number,
26+
React.RefSetter<mixed>,
27+
] {
28+
let getRevision: ?() => ?number;
29+
30+
function getShadowNodeReferenceCount() {
31+
if (getRevision == null) {
32+
throw new Error('ShadowNode revision getter was not initialized.');
33+
}
34+
return getRevision();
35+
}
36+
37+
function ref(instance: mixed | null) {
38+
if (instance == null) {
39+
return;
40+
}
41+
const element = ensureInstance(instance, ReactNativeElement);
42+
if (getRevision != null) {
43+
throw new Error('ShadowNode revision getter was already initialized.');
44+
}
45+
getRevision = createShadowNodeRevisionGetter(element);
46+
}
47+
48+
return [getShadowNodeReferenceCount, ref];
49+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
12+
13+
import {createShadowNodeReferenceGetterRef} from '../ShadowNodeRevisionGetter';
14+
import * as Fantom from '@react-native/fantom';
15+
import * as React from 'react';
16+
import {ScrollView, View} from 'react-native';
17+
18+
test('base case when cloning results in revision +1', () => {
19+
const root = Fantom.createRoot();
20+
21+
const [getRevision, ref] = createShadowNodeReferenceGetterRef();
22+
23+
Fantom.runTask(() => {
24+
root.render(<View ref={ref} />);
25+
});
26+
27+
expect(getRevision()).toBe(1);
28+
29+
Fantom.runTask(() => {
30+
root.render(<View nativeID="updated" ref={ref} />);
31+
});
32+
33+
expect(getRevision()).toBe(2);
34+
});
35+
36+
test('changing height of the top item in ScrollView results in excessive cloning', () => {
37+
const root = Fantom.createRoot();
38+
const [getRevision, ref] = createShadowNodeReferenceGetterRef();
39+
40+
Fantom.runTask(() => {
41+
root.render(
42+
<ScrollView>
43+
<View id="Sibling" style={{height: 1}} />
44+
<View id="A">
45+
<View id="B">
46+
<View id="C">
47+
<View id="D" ref={ref} />
48+
</View>
49+
</View>
50+
</View>
51+
</ScrollView>,
52+
);
53+
});
54+
55+
expect(getRevision()).toBe(1);
56+
57+
Fantom.runTask(() => {
58+
root.render(
59+
<ScrollView>
60+
<View id="Sibling" style={{height: 2}} />
61+
<View id="A">
62+
<View id="B">
63+
<View id="C">
64+
<View id="D" ref={ref} />
65+
</View>
66+
</View>
67+
</View>
68+
</ScrollView>,
69+
);
70+
});
71+
72+
// TODO(T225268793): the below assertion should be: `expect(getRevision()).toBe(1);`
73+
expect(getRevision()).toBe(2);
74+
});

packages/react-native/src/private/testing/fantom/specs/NativeFantom.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ interface Spec extends TurboModule {
9696
createShadowNodeReferenceCounter(
9797
shadowNode: mixed /* ShadowNode */,
9898
): () => number;
99+
createShadowNodeRevisionGetter(
100+
shadowNode: mixed /* ShadowNode */,
101+
): () => ?number;
99102
saveJSMemoryHeapSnapshot: (filePath: string) => void;
100103
}
101104

0 commit comments

Comments
 (0)