Skip to content

Commit ec402de

Browse files
authored
Merge branch 'facebook:main' into fix/android-translucent-dashed-borders
2 parents 5b42fed + 320e3bd commit ec402de

16 files changed

Lines changed: 328 additions & 46 deletions

File tree

packages/react-native/Libraries/Core/Devtools/loadBundleFromServer.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import getDevServer from './getDevServer';
1515

1616
declare var global: {
1717
globalEvalWithSourceUrl?: (string, string) => unknown,
18+
__BUNDLE_LOADER_REPORTER__?: {
19+
onStart: (url: string | URL) => void,
20+
onSuccess: (url: string | URL) => void,
21+
onError: (url: string | URL, error: Error) => void,
22+
},
1823
...
1924
};
2025

@@ -153,6 +158,7 @@ export default function loadBundleFromServer(
153158
}
154159
DevLoadingView.showMessage('Downloading...', 'load');
155160
++pendingRequests;
161+
global.__BUNDLE_LOADER_REPORTER__?.onStart(requestUrl);
156162

157163
loadPromise = asyncRequest(requestUrl)
158164
.then<void>(({body, headers}) => {
@@ -183,9 +189,11 @@ export default function loadBundleFromServer(
183189
// eslint-disable-next-line no-eval
184190
eval(body);
185191
}
192+
global.__BUNDLE_LOADER_REPORTER__?.onSuccess(requestUrl);
186193
})
187194
.catch<void>(e => {
188195
cachedPromisesByUrl.delete(requestUrl);
196+
global.__BUNDLE_LOADER_REPORTER__?.onError(requestUrl, e);
189197
throw e;
190198
})
191199
.finally(() => {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,14 @@ internal constructor(
608608
return
609609
}
610610

611-
val viewState = getViewState(reactTag)
611+
val viewState = getNullableViewState(reactTag)
612+
if (viewState == null) {
613+
ReactSoftExceptionLogger.logSoftException(
614+
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
615+
ReactNoCrashSoftException("Unable to find viewState for tag $reactTag for updateProps"),
616+
)
617+
return
618+
}
612619

613620
if (
614621
ReactNativeFeatureFlags.overrideBySynchronousMountPropsAtMountingAndroid() &&
@@ -624,7 +631,7 @@ internal constructor(
624631
viewState.currentProps = ReactStylesDiffMap(props)
625632
}
626633

627-
val view: View = checkNotNull(viewState.view) { "Unable to find view for tag [$reactTag]" }
634+
val view: View = viewState.view ?: return
628635
checkNotNull(viewState.viewManager).updateProperties(view, viewState.currentProps)
629636
}
630637

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package com.facebook.react.uimanager
99

10+
import android.annotation.SuppressLint
1011
import android.annotation.TargetApi
1112
import android.graphics.BlendMode
1213
import android.graphics.BlendModeColorFilter
@@ -30,6 +31,7 @@ import kotlin.math.sin
3031
*
3132
* @see <a href="https://www.w3.org/TR/filter-effects-1/">CSS Filter Effects Module Level 1</a>
3233
*/
34+
@SuppressLint("UseRequiresApi")
3335
@TargetApi(31)
3436
internal object FilterHelper {
3537

@@ -104,7 +106,7 @@ internal object FilterHelper {
104106
}
105107

106108
for (i in 0 until filters.size()) {
107-
val filter = filters.getMap(i)!!.entryIterator.next()
109+
val filter = checkNotNull(filters.getMap(i)).entryIterator.next()
108110
val filterName = filter.key
109111
if (filterName == "blur" || filterName == "dropShadow") {
110112
return false

packages/react-native/ReactApple/RCTSwiftUI/RCTSwiftUI.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Pod::Spec.new do |s|
2525
s.author = "Meta Platforms, Inc. and its affiliates"
2626
s.platforms = min_supported_versions
2727
s.source = source
28-
s.source_files = "*.{h,m,swift}"
28+
s.source_files = podspec_sources("*.{h,m,swift}", "")
2929
s.public_header_files = "*.h"
3030
s.module_name = "RCTSwiftUI"
3131
s.header_dir = "RCTSwiftUI"

packages/react-native/ReactCommon/React-Fabric.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Pod::Spec.new do |s|
6060
ss.source_files = podspec_sources("react/renderer/animated/**/*.{m,mm,cpp,h}", "react/renderer/animated/**/*.{h}")
6161
ss.exclude_files = "react/renderer/animated/tests"
6262
ss.header_dir = "react/renderer/animated"
63+
ss.header_mappings_dir = "react/renderer/animated"
6364
end
6465

6566
s.subspec "animations" do |ss|
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
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+
8+
#include <algorithm>
9+
#include <vector>
10+
11+
#include <gtest/gtest.h>
12+
13+
#include <react/renderer/animations/utils.h>
14+
#include <react/renderer/mounting/ShadowViewMutation.h>
15+
16+
namespace facebook::react {
17+
18+
namespace {
19+
20+
ShadowView makeShadowView(Tag tag) {
21+
ShadowView sv{};
22+
sv.tag = tag;
23+
return sv;
24+
}
25+
26+
} // namespace
27+
28+
// Verify strict weak ordering: irreflexivity
29+
// comp(a, a) must be false
30+
TEST(MutationComparatorTest, Irreflexivity) {
31+
auto sv1 = makeShadowView(1);
32+
auto sv2 = makeShadowView(2);
33+
34+
// Same-type mutations where the fallback path is exercised
35+
auto update1 = ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
36+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update1, update1));
37+
38+
auto create1 = ShadowViewMutation::CreateMutation(sv1);
39+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(create1, create1));
40+
41+
auto insert1 =
42+
ShadowViewMutation::InsertMutation(/*parentTag=*/10, sv1, /*index=*/0);
43+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(insert1, insert1));
44+
45+
auto remove1 =
46+
ShadowViewMutation::RemoveMutation(/*parentTag=*/10, sv1, /*index=*/0);
47+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(remove1, remove1));
48+
49+
auto del1 = ShadowViewMutation::DeleteMutation(sv1);
50+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(del1, del1));
51+
}
52+
53+
// Verify strict weak ordering: asymmetry
54+
// If comp(a, b) then !comp(b, a)
55+
TEST(MutationComparatorTest, Asymmetry) {
56+
auto sv1 = makeShadowView(1);
57+
auto sv2 = makeShadowView(2);
58+
auto sv3 = makeShadowView(3);
59+
60+
// Two updates with same type but different parentTags (fallback path)
61+
auto update1 = ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
62+
auto update2 = ShadowViewMutation::UpdateMutation(sv1, sv3, /*parentTag=*/20);
63+
64+
bool a_before_b = shouldFirstComeBeforeSecondMutation(update1, update2);
65+
bool b_before_a = shouldFirstComeBeforeSecondMutation(update2, update1);
66+
67+
// Exactly one must be true (asymmetry)
68+
EXPECT_NE(a_before_b, b_before_a);
69+
}
70+
71+
// Verify strict weak ordering: transitivity
72+
// If comp(a, b) and comp(b, c) then comp(a, c)
73+
TEST(MutationComparatorTest, Transitivity) {
74+
auto sv1 = makeShadowView(1);
75+
auto sv2 = makeShadowView(2);
76+
auto sv3 = makeShadowView(3);
77+
78+
auto update1 = ShadowViewMutation::UpdateMutation(sv1, sv1, /*parentTag=*/10);
79+
auto update2 = ShadowViewMutation::UpdateMutation(sv1, sv1, /*parentTag=*/20);
80+
auto update3 = ShadowViewMutation::UpdateMutation(sv1, sv1, /*parentTag=*/30);
81+
82+
bool a_b = shouldFirstComeBeforeSecondMutation(update1, update2);
83+
bool b_c = shouldFirstComeBeforeSecondMutation(update2, update3);
84+
bool a_c = shouldFirstComeBeforeSecondMutation(update1, update3);
85+
86+
if (a_b && b_c) {
87+
EXPECT_TRUE(a_c) << "Transitivity violated: a<b and b<c but not a<c";
88+
}
89+
}
90+
91+
// Verify type-based ordering rules
92+
TEST(MutationComparatorTest, TypeOrdering) {
93+
auto sv1 = makeShadowView(1);
94+
auto sv2 = makeShadowView(2);
95+
96+
auto create = ShadowViewMutation::CreateMutation(sv1);
97+
auto insert =
98+
ShadowViewMutation::InsertMutation(/*parentTag=*/10, sv1, /*index=*/0);
99+
auto remove =
100+
ShadowViewMutation::RemoveMutation(/*parentTag=*/10, sv1, /*index=*/0);
101+
auto update = ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
102+
auto del = ShadowViewMutation::DeleteMutation(sv1);
103+
104+
// Delete always comes last
105+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(del, create));
106+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(del, insert));
107+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(del, remove));
108+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(create, del));
109+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(insert, del));
110+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(remove, del));
111+
112+
// Remove comes before Insert
113+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(remove, insert));
114+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(insert, remove));
115+
116+
// Create comes before Insert
117+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(create, insert));
118+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(insert, create));
119+
120+
// Remove comes before Update
121+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(remove, update));
122+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update, remove));
123+
}
124+
125+
// Verify removes on same parent are sorted by descending index
126+
TEST(MutationComparatorTest, RemovesSameParentDescendingIndex) {
127+
auto sv1 = makeShadowView(1);
128+
auto sv2 = makeShadowView(2);
129+
130+
auto remove_idx0 =
131+
ShadowViewMutation::RemoveMutation(/*parentTag=*/10, sv1, /*index=*/0);
132+
auto remove_idx5 =
133+
ShadowViewMutation::RemoveMutation(/*parentTag=*/10, sv2, /*index=*/5);
134+
135+
// Higher index should come first
136+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(remove_idx5, remove_idx0));
137+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(remove_idx0, remove_idx5));
138+
}
139+
140+
// Verify the deterministic fallback uses parentTag, then child tags
141+
TEST(MutationComparatorTest, DeterministicFallbackByParentTag) {
142+
auto sv1 = makeShadowView(1);
143+
auto sv2 = makeShadowView(2);
144+
145+
// Two updates with same type, different parentTags
146+
auto update_p10 =
147+
ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
148+
auto update_p20 =
149+
ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/20);
150+
151+
// Lower parentTag comes first
152+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(update_p10, update_p20));
153+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update_p20, update_p10));
154+
}
155+
156+
TEST(MutationComparatorTest, DeterministicFallbackByNewChildTag) {
157+
auto sv1 = makeShadowView(1);
158+
auto sv2 = makeShadowView(2);
159+
auto sv3 = makeShadowView(3);
160+
161+
// Same parentTag, different newChildShadowView tags
162+
auto update_new2 =
163+
ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
164+
auto update_new3 =
165+
ShadowViewMutation::UpdateMutation(sv1, sv3, /*parentTag=*/10);
166+
167+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(update_new2, update_new3));
168+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update_new3, update_new2));
169+
}
170+
171+
TEST(MutationComparatorTest, DeterministicFallbackByOldChildTag) {
172+
auto sv1 = makeShadowView(1);
173+
auto sv2 = makeShadowView(2);
174+
auto sv3 = makeShadowView(3);
175+
176+
// Same parentTag, same newChildShadowView tag, different oldChildShadowView
177+
// tags
178+
auto update_old1 =
179+
ShadowViewMutation::UpdateMutation(sv1, sv3, /*parentTag=*/10);
180+
auto update_old2 =
181+
ShadowViewMutation::UpdateMutation(sv2, sv3, /*parentTag=*/10);
182+
183+
EXPECT_TRUE(shouldFirstComeBeforeSecondMutation(update_old1, update_old2));
184+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update_old2, update_old1));
185+
}
186+
187+
// Verify equal mutations return false (stability)
188+
TEST(MutationComparatorTest, EqualMutationsReturnFalse) {
189+
auto sv1 = makeShadowView(1);
190+
auto sv2 = makeShadowView(2);
191+
192+
auto update_a =
193+
ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
194+
auto update_b =
195+
ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10);
196+
197+
// Two mutations with identical properties should return false (not less-than)
198+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update_a, update_b));
199+
EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update_b, update_a));
200+
}
201+
202+
// Verify std::stable_sort doesn't crash with the comparator
203+
// (the original bug was a SIGSEGV during sort)
204+
TEST(MutationComparatorTest, StableSortDoesNotCrash) {
205+
ShadowViewMutation::List mutations;
206+
207+
// Build a list with various mutation types and properties
208+
for (int parent = 0; parent < 5; parent++) {
209+
for (int child = 0; child < 10; child++) {
210+
auto sv_old = makeShadowView(child);
211+
auto sv_new = makeShadowView(child + 100);
212+
213+
mutations.push_back(
214+
ShadowViewMutation::UpdateMutation(
215+
sv_old, sv_new, /*parentTag=*/parent));
216+
mutations.push_back(
217+
ShadowViewMutation::InsertMutation(
218+
/*parentTag=*/parent, sv_new, /*index=*/child));
219+
mutations.push_back(
220+
ShadowViewMutation::RemoveMutation(
221+
/*parentTag=*/parent, sv_old, /*index=*/child));
222+
}
223+
}
224+
mutations.push_back(ShadowViewMutation::CreateMutation(makeShadowView(999)));
225+
mutations.push_back(ShadowViewMutation::DeleteMutation(makeShadowView(998)));
226+
227+
// This should not crash (the original bug was SIGSEGV here)
228+
EXPECT_NO_FATAL_FAILURE(
229+
std::stable_sort(
230+
mutations.begin(),
231+
mutations.end(),
232+
&shouldFirstComeBeforeSecondMutation));
233+
234+
// Verify ordering invariants after sort
235+
bool seen_delete = false;
236+
for (const auto& m : mutations) {
237+
if (m.type == ShadowViewMutation::Type::Delete) {
238+
seen_delete = true;
239+
} else if (seen_delete) {
240+
FAIL() << "Non-delete mutation found after a delete mutation";
241+
}
242+
}
243+
}
244+
245+
// Stress test: sort a large list of mutations with duplicates
246+
TEST(MutationComparatorTest, StableSortLargeListWithDuplicates) {
247+
ShadowViewMutation::List mutations;
248+
249+
// Create many mutations with overlapping properties to stress the comparator
250+
for (int i = 0; i < 200; i++) {
251+
auto sv = makeShadowView(i % 10); // Deliberately create duplicates
252+
auto sv2 = makeShadowView((i + 1) % 10);
253+
int parent = i % 5;
254+
255+
mutations.push_back(ShadowViewMutation::UpdateMutation(sv, sv2, parent));
256+
if (i % 3 == 0) {
257+
mutations.push_back(
258+
ShadowViewMutation::InsertMutation(parent, sv, i % 20));
259+
}
260+
if (i % 4 == 0) {
261+
mutations.push_back(
262+
ShadowViewMutation::RemoveMutation(parent, sv, i % 20));
263+
}
264+
}
265+
266+
EXPECT_NO_FATAL_FAILURE(
267+
std::stable_sort(
268+
mutations.begin(),
269+
mutations.end(),
270+
&shouldFirstComeBeforeSecondMutation));
271+
}
272+
273+
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/animations/utils.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,18 @@ static inline bool shouldFirstComeBeforeSecondMutation(
9696
}
9797
}
9898

99-
return &lhs < &rhs;
99+
// Deterministic fallback: when no type-specific rule applies, order by
100+
// mutation properties to satisfy strict weak ordering.
101+
if (lhs.parentTag != rhs.parentTag) {
102+
return lhs.parentTag < rhs.parentTag;
103+
}
104+
if (lhs.newChildShadowView.tag != rhs.newChildShadowView.tag) {
105+
return lhs.newChildShadowView.tag < rhs.newChildShadowView.tag;
106+
}
107+
if (lhs.oldChildShadowView.tag != rhs.oldChildShadowView.tag) {
108+
return lhs.oldChildShadowView.tag < rhs.oldChildShadowView.tag;
109+
}
110+
return false;
100111
}
101112

102113
std::pair<Float, Float>

0 commit comments

Comments
 (0)