From 46278e30e32a30b174a5d0cdcf226c7eccda14ea Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 1 Sep 2025 08:38:27 -0700 Subject: [PATCH 0001/1110] Dedupe Accessibility enum string conversions (#53550) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53550 Noticed some duplication between `getDiffProps` and `accessibilityPropsConversion` Changelog: [Internal] Reviewed By: lenaic, rshest Differential Revision: D81435037 fbshipit-source-id: b2701f1aec5e647c165a0212f6180edba90fd9f9 --- .../components/view/AccessibilityPrimitives.h | 62 +------------------ .../view/accessibilityPropsConversions.h | 56 +++++++++++++++++ .../renderer/components/view/conversions.h | 2 +- .../components/view/HostPlatformViewProps.cpp | 43 +------------ 4 files changed, 62 insertions(+), 101 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h index eb5232584ac9..b5d7010a28b6 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h @@ -12,8 +12,6 @@ #include #include -#include - namespace facebook::react { enum class AccessibilityTraits : uint32_t { @@ -55,27 +53,6 @@ struct AccessibilityAction { std::optional label{}; }; -inline std::string toString(const AccessibilityAction& accessibilityAction) { - std::string result = accessibilityAction.name; - if (accessibilityAction.label.has_value()) { - result += ": '" + accessibilityAction.label.value() + "'"; - } - return result; -} - -inline std::string toString( - std::vector accessibilityActions) { - std::string result = "["; - for (size_t i = 0; i < accessibilityActions.size(); i++) { - result += toString(accessibilityActions[i]); - if (i < accessibilityActions.size() - 1) { - result += ", "; - } - } - result += "]"; - return result; -} - inline static bool operator==( const AccessibilityAction& lhs, const AccessibilityAction& rhs) { @@ -110,29 +87,6 @@ constexpr bool operator!=( return !(rhs == lhs); } -#if RN_DEBUG_STRING_CONVERTIBLE -inline std::string toString(AccessibilityState::CheckedState state) { - switch (state) { - case AccessibilityState::Unchecked: - return "Unchecked"; - case AccessibilityState::Checked: - return "Checked"; - case AccessibilityState::Mixed: - return "Mixed"; - case AccessibilityState::None: - return "None"; - } -} - -inline std::string toString(const AccessibilityState& accessibilityState) { - return "{disabled:" + toString(accessibilityState.disabled) + - ",selected:" + toString(accessibilityState.selected) + - ",checked:" + toString(accessibilityState.checked) + - ",busy:" + toString(accessibilityState.busy) + - ",expanded:" + toString(accessibilityState.expanded) + "}"; -} -#endif - struct AccessibilityLabelledBy { std::vector value{}; }; @@ -182,19 +136,7 @@ enum class AccessibilityLiveRegion : uint8_t { Assertive, }; -inline std::string toString( - const AccessibilityLiveRegion& accessibilityLiveRegion) { - switch (accessibilityLiveRegion) { - case AccessibilityLiveRegion::None: - return "none"; - case AccessibilityLiveRegion::Polite: - return "polite"; - case AccessibilityLiveRegion::Assertive: - return "assertive"; - } -} - -enum class AccessibilityRole { +enum class AccessibilityRole : uint8_t { None, Button, Dropdownlist, @@ -237,7 +179,7 @@ enum class AccessibilityRole { Iconmenu, }; -enum class Role { +enum class Role : uint8_t { Alert, Alertdialog, Application, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h index 7b16f62dc543..8eb786fb02a5 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -782,4 +783,59 @@ inline void fromRawValue( result = Role::None; } +inline std::string toString(AccessibilityLiveRegion accessibilityLiveRegion) { + switch (accessibilityLiveRegion) { + case AccessibilityLiveRegion::None: + return "none"; + case AccessibilityLiveRegion::Polite: + return "polite"; + case AccessibilityLiveRegion::Assertive: + return "assertive"; + } +} + +#if RN_DEBUG_STRING_CONVERTIBLE +inline std::string toString(AccessibilityState::CheckedState state) { + switch (state) { + case AccessibilityState::Unchecked: + return "Unchecked"; + case AccessibilityState::Checked: + return "Checked"; + case AccessibilityState::Mixed: + return "Mixed"; + case AccessibilityState::None: + return "None"; + } +} + +inline std::string toString(const AccessibilityAction& accessibilityAction) { + std::string result = accessibilityAction.name; + if (accessibilityAction.label.has_value()) { + result += ": '" + accessibilityAction.label.value() + "'"; + } + return result; +} + +inline std::string toString( + std::vector accessibilityActions) { + std::string result = "["; + for (size_t i = 0; i < accessibilityActions.size(); i++) { + result += toString(accessibilityActions[i]); + if (i < accessibilityActions.size() - 1) { + result += ", "; + } + } + result += "]"; + return result; +} + +inline std::string toString(const AccessibilityState& accessibilityState) { + return "{disabled:" + toString(accessibilityState.disabled) + + ",selected:" + toString(accessibilityState.selected) + + ",checked:" + toString(accessibilityState.checked) + + ",busy:" + toString(accessibilityState.busy) + + ",expanded:" + toString(accessibilityState.expanded) + "}"; +} +#endif + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h index 349b1e332e03..0927a1a83bba 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -848,7 +848,7 @@ inline void fromRawValue( react_native_expect(false); } -inline std::string toString(const PointerEventsMode& value) { +inline std::string toString(PointerEventsMode value) { switch (value) { case PointerEventsMode::Auto: return "auto"; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index 395dbe5f1aef..30ae94e6b5b2 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -618,21 +618,7 @@ folly::dynamic HostPlatformViewProps::getDiffProps( } if (pointerEvents != oldProps->pointerEvents) { - std::string value; - switch (pointerEvents) { - case PointerEventsMode::BoxOnly: - result["pointerEvents"] = "box-only"; - break; - case PointerEventsMode::BoxNone: - result["pointerEvents"] = "box-none"; - break; - case PointerEventsMode::None: - result["pointerEvents"] = "none"; - break; - default: - result["pointerEvents"] = "auto"; - break; - } + result["pointerEvents"] = toString(pointerEvents); } if (hitSlop != oldProps->hitSlop) { @@ -917,17 +903,7 @@ folly::dynamic HostPlatformViewProps::getDiffProps( } if (accessibilityLiveRegion != oldProps->accessibilityLiveRegion) { - switch (accessibilityLiveRegion) { - case AccessibilityLiveRegion::Assertive: - result["accessibilityLiveRegion"] = "assertive"; - break; - case AccessibilityLiveRegion::Polite: - result["accessibilityLiveRegion"] = "polite"; - break; - case AccessibilityLiveRegion::None: - result["accessibilityLiveRegion"] = "none"; - break; - } + result["accessibilityLiveRegion"] = toString(accessibilityLiveRegion); } if (accessibilityHint != oldProps->accessibilityHint) { @@ -1003,20 +979,7 @@ folly::dynamic HostPlatformViewProps::getDiffProps( } if (importantForAccessibility != oldProps->importantForAccessibility) { - switch (importantForAccessibility) { - case ImportantForAccessibility::Auto: - result["importantForAccessibility"] = "auto"; - break; - case ImportantForAccessibility::Yes: - result["importantForAccessibility"] = "yes"; - break; - case ImportantForAccessibility::No: - result["importantForAccessibility"] = "no"; - break; - case ImportantForAccessibility::NoHideDescendants: - result["importantForAccessibility"] = "noHideDescendants"; - break; - } + result["importantForAccessibility"] = toString(importantForAccessibility); } return result; From 0a0b48b5fff508a1976604bedc931a1b5110a4f2 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Mon, 1 Sep 2025 08:39:22 -0700 Subject: [PATCH 0002/1110] Expose ListViewToken type as root export (#53539) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53539 Resolves https://github.com/react-native-community/discussions-and-proposals/discussions/893#discussioncomment-14190663. Changelog: [General][Added] - `ListViewToken` is now exposed when using `"react-native-strict-api"` Reviewed By: rshest Differential Revision: D81380882 fbshipit-source-id: 1da5c50eaec2f8dc4a8cde60e7441249556053a8 --- .../react-native/Libraries/Lists/FlatList.js | 16 +++---- .../Libraries/Lists/ViewabilityHelper.js | 2 +- .../Libraries/Lists/VirtualizedList.js | 1 + packages/react-native/ReactNativeApi.d.ts | 43 ++++++++++--------- packages/react-native/index.js.flow | 1 + packages/virtualized-lists/index.js | 2 +- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/packages/react-native/Libraries/Lists/FlatList.js b/packages/react-native/Libraries/Lists/FlatList.js index ed708b52df6c..9d01d2e08dfc 100644 --- a/packages/react-native/Libraries/Lists/FlatList.js +++ b/packages/react-native/Libraries/Lists/FlatList.js @@ -13,8 +13,8 @@ import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; import type { ListRenderItem, ListRenderItemInfo, + ListViewToken, ViewabilityConfigCallbackPair, - ViewToken, VirtualizedListProps, } from '@react-native/virtualized-lists'; @@ -573,7 +573,7 @@ class FlatList extends React.PureComponent> { return keyExtractor(items, index); }; - _pushMultiColumnViewable(arr: Array, v: ViewToken): void { + _pushMultiColumnViewable(arr: Array, v: ListViewToken): void { const numColumns = numColumnsOrDefault(this.props.numColumns); const keyExtractor = this.props.keyExtractor ?? defaultKeyExtractor; v.item.forEach((item, ii) => { @@ -585,22 +585,22 @@ class FlatList extends React.PureComponent> { _createOnViewableItemsChanged( onViewableItemsChanged: ?(info: { - viewableItems: Array, - changed: Array, + viewableItems: Array, + changed: Array, ... }) => void, // $FlowFixMe[missing-local-annot] ) { return (info: { - viewableItems: Array, - changed: Array, + viewableItems: Array, + changed: Array, ... }) => { const numColumns = numColumnsOrDefault(this.props.numColumns); if (onViewableItemsChanged) { if (numColumns > 1) { - const changed: Array = []; - const viewableItems: Array = []; + const changed: Array = []; + const viewableItems: Array = []; info.viewableItems.forEach(v => this._pushMultiColumnViewable(viewableItems, v), ); diff --git a/packages/react-native/Libraries/Lists/ViewabilityHelper.js b/packages/react-native/Libraries/Lists/ViewabilityHelper.js index e20900991e95..60ebd2475150 100644 --- a/packages/react-native/Libraries/Lists/ViewabilityHelper.js +++ b/packages/react-native/Libraries/Lists/ViewabilityHelper.js @@ -11,7 +11,7 @@ 'use strict'; export type { - ViewToken, + ListViewToken as ViewToken, ViewabilityConfig, ViewabilityConfigCallbackPair, } from '@react-native/virtualized-lists'; diff --git a/packages/react-native/Libraries/Lists/VirtualizedList.js b/packages/react-native/Libraries/Lists/VirtualizedList.js index 45b6580afb39..df18a2742993 100644 --- a/packages/react-native/Libraries/Lists/VirtualizedList.js +++ b/packages/react-native/Libraries/Lists/VirtualizedList.js @@ -19,6 +19,7 @@ const VirtualizedListComponent: VirtualizedListType = export type { ListRenderItemInfo, ListRenderItem, + ListViewToken, Separators, VirtualizedListProps, } from '@react-native/virtualized-lists'; diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 51c48dadd3dc..058b47d8d864 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0a0286ba0bc29af2abd2e8fd836077fb>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -3089,6 +3089,13 @@ declare type ListRenderItemInfo = { item: ItemT separators: Separators } +declare type ListViewToken = { + index: number | undefined + isViewable: boolean + item: any + key: string + section?: any +} declare type LogBox = typeof LogBox declare type LogData = { readonly category: Category @@ -3511,8 +3518,8 @@ declare type OptionalVirtualizedListProps = { onStartReached?: (info: { distanceFromStart: number }) => void onStartReachedThreshold?: number onViewableItemsChanged?: (info: { - changed: Array - viewableItems: Array + changed: Array + viewableItems: Array }) => void persistentScrollbar?: boolean progressViewOffset?: number @@ -5679,8 +5686,8 @@ declare type ViewabilityConfig = { declare type ViewabilityConfigCallbackPair = { viewabilityConfig: ViewabilityConfig onViewableItemsChanged: (info: { - changed: Array - viewableItems: Array + changed: Array + viewableItems: Array }) => void } declare class ViewabilityHelper_default { @@ -5705,10 +5712,10 @@ declare class ViewabilityHelper_default { index: number, isViewable: boolean, props: CellMetricProps, - ) => ViewToken, + ) => ListViewToken, onViewableItemsChanged: ($$PARAM_0$$: { - changed: Array - viewableItems: Array + changed: Array + viewableItems: Array }) => void, renderRange?: { first: number @@ -5793,13 +5800,6 @@ declare type ViewPropsIOS = { } declare type ViewStyle = ____ViewStyle_Internal declare type ViewStyleProp = ____ViewStyleProp_Internal -declare type ViewToken = { - index: number | undefined - isViewable: boolean - item: any - key: string - section?: any -} declare type VirtualizedList = typeof VirtualizedList declare class VirtualizedList_default extends StateSafePureComponent_default< VirtualizedListProps, @@ -6013,8 +6013,8 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // 714df8ad - FlatListProps, // e3e724ea + FlatList, // 4f1b407e + FlatListProps, // b225fb7a FocusEvent, // 529b43eb FontVariant, // 7c7558bb GestureResponderEvent, // b466f6d6 @@ -6068,6 +6068,7 @@ export { Linking, // 292de0a0 ListRenderItem, // b5353fd8 ListRenderItemInfo, // e8595b03 + ListViewToken, // 833d3481 LogBox, // b58880c6 LogData, // 89af6d4c MeasureInWindowOnSuccessCallback, // a285f598 @@ -6151,9 +6152,9 @@ export { ScrollViewPropsIOS, // d83c9733 ScrollViewScrollToOptions, // 3313411e SectionBase, // 0ccaedac - SectionList, // cc6dec0b + SectionList, // a1a4786b SectionListData, // 1c80bb2e - SectionListProps, // 97fcf95a + SectionListProps, // 7fb5371e SectionListRenderItem, // cffebb53 SectionListRenderItemInfo, // 946c2128 Separators, // 6a45f7e3 @@ -6218,9 +6219,9 @@ export { ViewStyle, // c2db0e6e VirtualViewMode, // 85a69ef6 VirtualizedList, // 4d513939 - VirtualizedListProps, // 8efa6d8e + VirtualizedListProps, // be716140 VirtualizedSectionList, // 446ba0df - VirtualizedSectionListProps, // c5e64f83 + VirtualizedSectionListProps, // a6899dfb WrapperComponentProvider, // 9cf3844c codegenNativeCommands, // e16d62f7 codegenNativeComponent, // ed4c8103 diff --git a/packages/react-native/index.js.flow b/packages/react-native/index.js.flow index fc90158a036e..0997abb050f9 100644 --- a/packages/react-native/index.js.flow +++ b/packages/react-native/index.js.flow @@ -184,6 +184,7 @@ export {default as View} from './Libraries/Components/View/View'; export type { ListRenderItemInfo, ListRenderItem, + ListViewToken, Separators, VirtualizedListProps, } from './Libraries/Lists/VirtualizedList'; diff --git a/packages/virtualized-lists/index.js b/packages/virtualized-lists/index.js index 9e0ab87d32fc..9bcac9976b7f 100644 --- a/packages/virtualized-lists/index.js +++ b/packages/virtualized-lists/index.js @@ -19,7 +19,7 @@ import {typeof VirtualizedListContextResetter as VirtualizedListContextResetterT import {keyExtractor} from './Lists/VirtualizeUtils'; export type { - ViewToken, + ViewToken as ListViewToken, ViewabilityConfig, ViewabilityConfigCallbackPair, ViewabilityConfigCallbackPairs, From 05be3742d4cfa79555962d5fc926546f004167b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Sep 2025 09:18:19 -0700 Subject: [PATCH 0003/1110] Define Flow types for Performance APIs (#53433) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53433 Changelog: [internal] This adds the definitions for the Web Performance APIs in the global scope. Reviewed By: zeyap Differential Revision: D80811659 fbshipit-source-id: a81117a27a480ba03f8feb2e813a3a66a10307f9 --- packages/react-native/flow/bom.js.flow | 157 +++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/packages/react-native/flow/bom.js.flow b/packages/react-native/flow/bom.js.flow index 2d93d0e6fa00..9b0d4d94f494 100644 --- a/packages/react-native/flow/bom.js.flow +++ b/packages/react-native/flow/bom.js.flow @@ -88,12 +88,169 @@ declare var navigator: Navigator; // https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp declare type DOMHighResTimeStamp = number; +type PerformanceEntryFilterOptions = { + entryType: string, + name: string, + ... +}; + +// https://www.w3.org/TR/performance-timeline-2/ +declare class PerformanceEntry { + duration: DOMHighResTimeStamp; + entryType: string; + name: string; + startTime: DOMHighResTimeStamp; + toJSON(): string; +} + +// https://w3c.github.io/user-timing/#performancemark +declare class PerformanceMark extends PerformanceEntry { + constructor(name: string, markOptions?: PerformanceMarkOptions): void; + +detail: mixed; +} + +// https://w3c.github.io/user-timing/#performancemeasure +declare class PerformanceMeasure extends PerformanceEntry { + +detail: mixed; +} + +// https://w3c.github.io/server-timing/#the-performanceservertiming-interface +declare class PerformanceServerTiming { + description: string; + duration: DOMHighResTimeStamp; + name: string; + toJSON(): string; +} + +// https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming +// https://w3c.github.io/server-timing/#extension-to-the-performanceresourcetiming-interface +declare class PerformanceResourceTiming extends PerformanceEntry { + connectEnd: number; + connectStart: number; + decodedBodySize: number; + domainLookupEnd: number; + domainLookupStart: number; + encodedBodySize: number; + fetchStart: number; + initiatorType: string; + nextHopProtocol: string; + redirectEnd: number; + redirectStart: number; + requestStart: number; + responseEnd: number; + responseStart: number; + secureConnectionStart: number; + serverTiming: Array; + transferSize: number; + workerStart: number; +} + +// https://w3c.github.io/event-timing/#sec-performance-event-timing +declare class PerformanceEventTiming extends PerformanceEntry { + cancelable: boolean; + interactionId: number; + processingEnd: number; + processingStart: number; + target: ?Node; +} + +// https://w3c.github.io/longtasks/#taskattributiontiming +declare class TaskAttributionTiming extends PerformanceEntry { + containerId: string; + containerName: string; + containerSrc: string; + containerType: string; +} + +// https://w3c.github.io/longtasks/#sec-PerformanceLongTaskTiming +declare class PerformanceLongTaskTiming extends PerformanceEntry { + attribution: $ReadOnlyArray; +} + +// https://www.w3.org/TR/user-timing/#extensions-performance-interface +declare type PerformanceMarkOptions = { + detail?: mixed, + startTime?: number, +}; + +declare type PerformanceMeasureOptions = { + detail?: mixed, + duration?: number, + end?: number | string, + start?: number | string, +}; + +type EventCountsForEachCallbackType = + | (() => void) + | ((value: number) => void) + | ((value: number, key: string) => void) + | ((value: number, key: string, map: Map) => void); + +// https://www.w3.org/TR/event-timing/#eventcounts +declare interface EventCounts { + entries(): Iterator<[string, number]>; + + forEach(callback: EventCountsForEachCallbackType): void; + get(key: string): ?number; + has(key: string): boolean; + keys(): Iterator; + size: number; + values(): Iterator; +} + declare class Performance { + clearMarks(name?: string): void; + + clearMeasures(name?: string): void; + + eventCounts: EventCounts; + getEntries: ( + options?: PerformanceEntryFilterOptions, + ) => Array; + getEntriesByName: (name: string, type?: string) => Array; + getEntriesByType: (type: string) => Array; + mark(name: string, options?: PerformanceMarkOptions): PerformanceMark; + measure( + name: string, + startMarkOrOptions?: string | PerformanceMeasureOptions, + endMark?: string, + ): PerformanceMeasure; now: () => DOMHighResTimeStamp; + toJSON(): string; } declare var performance: Performance; +type PerformanceEntryList = Array; + +declare interface PerformanceObserverEntryList { + getEntries(): PerformanceEntryList; + getEntriesByName(name: string, type: ?string): PerformanceEntryList; + getEntriesByType(type: string): PerformanceEntryList; +} + +type PerformanceObserverInit = { + buffered?: boolean, + entryTypes?: Array, + type?: string, + ... +}; + +declare class PerformanceObserver { + constructor( + callback: ( + entries: PerformanceObserverEntryList, + observer: PerformanceObserver, + ) => mixed, + ): void; + + disconnect(): void; + observe(options: ?PerformanceObserverInit): void; + static supportedEntryTypes: Array; + + takeRecords(): PerformanceEntryList; +} + type FormDataEntryValue = string | File; declare class FormData { From 8ed0fa8dda5fc1ce16214020fad2727062208143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Sep 2025 09:18:19 -0700 Subject: [PATCH 0004/1110] Remove unnecessary references to internal types in performance tests (#53427) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53427 Changelog: [internal] Migrate the imported types to the globally defined ones, so we follow the good practice of only accessing the public API in Fantom tests. Reviewed By: rshest Differential Revision: D80807160 fbshipit-source-id: 77d792b56b53c8da8409dd9133cd111afb8084f1 --- .../performance/__tests__/EventTimingAPI-itest.js | 7 ------- .../performance/__tests__/LongTasksAPI-itest.js | 7 +------ .../__tests__/Performance-benchmark-itest.js | 4 ---- .../__tests__/PerformanceObserver-itest.js | 9 --------- .../performance/__tests__/UserTiming-itest.js | 12 +++--------- 5 files changed, 4 insertions(+), 35 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js index 5c46106f95d7..886a5da84811 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js @@ -10,24 +10,17 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import type Performance from 'react-native/src/private/webapis/performance/Performance'; -import type {PerformanceObserverEntryList} from 'react-native/src/private/webapis/performance/PerformanceObserver'; - import MaybeNativePerformance from '../specs/NativePerformance'; import * as Fantom from '@react-native/fantom'; import nullthrows from 'nullthrows'; import {useState} from 'react'; import {Text, View} from 'react-native'; import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver'; -import {PerformanceEventTiming} from 'react-native/src/private/webapis/performance/EventTiming'; -import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver'; const NativePerformance = nullthrows(MaybeNativePerformance); setUpPerformanceObserver(); -declare var performance: Performance; - function sleep(ms: number) { const end = performance.now() + ms; while (performance.now() < end) {} diff --git a/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js index 80ff9cf54f04..990663ff03b2 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js @@ -10,15 +10,10 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import type { - PerformanceObserverCallbackOptions, - PerformanceObserverEntryList, -} from 'react-native/src/private/webapis/performance/PerformanceObserver'; +import type {PerformanceObserverCallbackOptions} from '../PerformanceObserver'; import * as Fantom from '@react-native/fantom'; import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver'; -import {PerformanceLongTaskTiming} from 'react-native/src/private/webapis/performance/LongTasks'; -import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver'; setUpPerformanceObserver(); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js index d69768d8d717..2829b8f94cf4 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js @@ -10,12 +10,8 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import type Performance from 'react-native/src/private/webapis/performance/Performance'; - import * as Fantom from '@react-native/fantom'; -declare var performance: Performance; - const clearMarksAndMeasures = () => { performance.clearMarks(); performance.clearMeasures(); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js index c3c65486e347..77af8cb8c83c 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js @@ -10,20 +10,11 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import type Performance from '../Performance'; -import type { - PerformanceObserver as PerformanceObserverT, - PerformanceObserverEntryList, -} from '../PerformanceObserver'; - import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; import * as Fantom from '@react-native/fantom'; setUpPerformanceObserver(); -declare var performance: Performance; -declare var PerformanceObserver: Class; - describe('PerformanceObserver', () => { it('receives notifications for marks and measures', () => { const callback = jest.fn(); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js index 0bc20e7c0127..00714de389fa 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js @@ -10,18 +10,12 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import type Performance from '../Performance'; -import type { - PerformanceEntryJSON, - PerformanceEntryList, -} from '../PerformanceEntry'; - import ensureInstance from '../../../__tests__/utilities/ensureInstance'; +import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; import DOMException from '../../errors/DOMException'; -import {PerformanceMark, PerformanceMeasure} from '../UserTiming'; import * as Fantom from '@react-native/fantom'; -declare var performance: Performance; +setUpPerformanceObserver(); function getThrownError(fn: () => mixed): mixed { try { @@ -32,7 +26,7 @@ function getThrownError(fn: () => mixed): mixed { throw new Error('Expected function to throw'); } -function toJSON(entries: PerformanceEntryList): Array { +function toJSON(entries: PerformanceEntryList): Array { return entries.map(entry => entry.toJSON()); } From 81f8b0a6bfc6b70b005908d54dc2883d946e4709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Sep 2025 09:18:19 -0700 Subject: [PATCH 0005/1110] Implement PerformanceObserver.takeRecords() (#53428) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53428 Changelog: [internal] This is the last method in `PerformanceObserver` to implement. For some reason we never added it, even though it was trivial. Reviewed By: rshest Differential Revision: D80717237 fbshipit-source-id: ae3bd243d0f3f0fe4f0705437d78d14c532515f7 --- .../performance/PerformanceObserver.js | 18 ++++++++++++++++- .../__tests__/PerformanceObserver-itest.js | 20 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index c576a4451f66..e2efde682ea2 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -145,6 +145,22 @@ export class PerformanceObserver { NativePerformance.disconnect(this.#nativeObserverHandle); } + takeRecords(): PerformanceEntryList { + let entries: PerformanceEntryList = []; + + if (this.#nativeObserverHandle != null) { + const rawEntries = NativePerformance.takeRecords( + this.#nativeObserverHandle, + true, + ); + if (rawEntries && rawEntries.length > 0) { + entries = rawEntries.map(rawToPerformanceEntry); + } + } + + return entries; + } + #createNativeObserver(): OpaqueNativeObserverHandle | null { this.#calledAtLeastOnce = false; @@ -154,7 +170,7 @@ export class PerformanceObserver { observerHandle, true, // sort records ); - if (!rawEntries) { + if (!rawEntries || rawEntries.length === 0) { return; } diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js index 77af8cb8c83c..70e91786f959 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js @@ -97,4 +97,24 @@ describe('PerformanceObserver', () => { expect(entries1.getEntries()[1]).toBe(measure); expect(entries2.getEntries()[1]).toBe(measure); }); + + describe('takeRecords()', () => { + it('provides all buffered events and clears the buffer', () => { + const callback = jest.fn(); + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['mark']}); + + Fantom.runTask(() => { + const entry = performance.mark('mark1'); + + const entries = observer.takeRecords(); + expect(entries.length).toBe(1); + // This is not supported yet + // expect(entries[0]).toBe(entry); + expect(entries[0]).toEqual(entry); + }); + + expect(callback).not.toHaveBeenCalled(); + }); + }); }); From bb508a4d942e31d2bcb39073ba338a9770d9e04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Sep 2025 09:18:19 -0700 Subject: [PATCH 0006/1110] Refactor PerformanceEntry and subclasses to use interfaces for initialization (#53429) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53429 Changelog: [internal] This is a refactor of the types in `PerformanceEntry` and subclasses to accept interfaces instead of objects. This allows us to pass down the init object from subclasses to the superclass without having to create intermediate objects. Additionally, this is also more semantically correct, as existing APIs don't need those options to be own properties of the init object. Existing benchmark for Performance doesn't show any significant impact. Reviewed By: rshest Differential Revision: D80800075 fbshipit-source-id: ab439d70f4db9ce60e3089d89ccb105a91e7ef48 --- .../webapis/performance/EventTiming.js | 25 ++++------ .../private/webapis/performance/LongTasks.js | 12 ++++- .../webapis/performance/Performance.js | 17 ++++--- .../webapis/performance/PerformanceEntry.js | 17 ++++--- .../webapis/performance/ResourceTiming.js | 34 ++++++------- .../private/webapis/performance/UserTiming.js | 49 ++++++++----------- .../internals/RawPerformanceEntry.js | 7 ++- 7 files changed, 80 insertions(+), 81 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/EventTiming.js b/packages/react-native/src/private/webapis/performance/EventTiming.js index 3f99141e223c..95657a3b0427 100644 --- a/packages/react-native/src/private/webapis/performance/EventTiming.js +++ b/packages/react-native/src/private/webapis/performance/EventTiming.js @@ -9,9 +9,9 @@ */ // flowlint unsafe-getters-setters:off - import type { DOMHighResTimeStamp, + PerformanceEntryInit, PerformanceEntryJSON, } from './PerformanceEntry'; @@ -29,25 +29,20 @@ export type PerformanceEventTimingJSON = { ... }; +export interface PerformanceEventTimingInit extends PerformanceEntryInit { + +processingStart?: DOMHighResTimeStamp; + +processingEnd?: DOMHighResTimeStamp; + +interactionId?: number; +} + export class PerformanceEventTiming extends PerformanceEntry { #processingStart: DOMHighResTimeStamp; #processingEnd: DOMHighResTimeStamp; #interactionId: number; - constructor(init: { - name: string, - startTime?: DOMHighResTimeStamp, - duration?: DOMHighResTimeStamp, - processingStart?: DOMHighResTimeStamp, - processingEnd?: DOMHighResTimeStamp, - interactionId?: number, - }) { - super({ - name: init.name, - entryType: 'event', - startTime: init.startTime ?? 0, - duration: init.duration ?? 0, - }); + constructor(init: PerformanceEventTimingInit) { + super('event', init); + this.#processingStart = init.processingStart ?? 0; this.#processingEnd = init.processingEnd ?? 0; this.#interactionId = init.interactionId ?? 0; diff --git a/packages/react-native/src/private/webapis/performance/LongTasks.js b/packages/react-native/src/private/webapis/performance/LongTasks.js index 4c745db150e7..9c27419dd072 100644 --- a/packages/react-native/src/private/webapis/performance/LongTasks.js +++ b/packages/react-native/src/private/webapis/performance/LongTasks.js @@ -9,8 +9,10 @@ */ // flowlint unsafe-getters-setters:off - -import type {PerformanceEntryJSON} from './PerformanceEntry'; +import type { + PerformanceEntryInit, + PerformanceEntryJSON, +} from './PerformanceEntry'; import {PerformanceEntry} from './PerformanceEntry'; @@ -25,7 +27,13 @@ export class TaskAttributionTiming extends PerformanceEntry {} const EMPTY_ATTRIBUTION: $ReadOnlyArray = Object.preventExtensions([]); +export interface PerformanceLongTaskTimingInit extends PerformanceEntryInit {} + export class PerformanceLongTaskTiming extends PerformanceEntry { + constructor(init: PerformanceEntryInit) { + super('longtask', init); + } + get attribution(): $ReadOnlyArray { return EMPTY_ATTRIBUTION; } diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 65c66dd2359b..6cd2fd5b0a74 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -64,12 +64,13 @@ const cachedGetMarkTime = NativePerformance.getMarkTime; const cachedNativeClearMarks = NativePerformance.clearMarks; const cachedNativeClearMeasures = NativePerformance.clearMeasures; -const MARK_OPTIONS_REUSABLE_OBJECT: {...PerformanceMarkOptions} = { +const MARK_OPTIONS_REUSABLE_OBJECT: PerformanceMarkOptions = { startTime: 0, detail: undefined, }; -const MEASURE_OPTIONS_REUSABLE_OBJECT: {...PerformanceMeasureInit} = { +const MEASURE_OPTIONS_REUSABLE_OBJECT: PerformanceMeasureInit = { + name: '', startTime: 0, duration: 0, detail: undefined, @@ -189,7 +190,9 @@ export default class Performance { resolvedDetail = structuredClone(detail); } + // $FlowExpectedError[cannot-write] MARK_OPTIONS_REUSABLE_OBJECT.startTime = resolvedStartTime; + // $FlowExpectedError[cannot-write] MARK_OPTIONS_REUSABLE_OBJECT.detail = resolvedDetail; const entry = new PerformanceMark( @@ -367,14 +370,16 @@ export default class Performance { } } + // $FlowExpectedError[cannot-write] + MEASURE_OPTIONS_REUSABLE_OBJECT.name = resolvedMeasureName; + // $FlowExpectedError[cannot-write] MEASURE_OPTIONS_REUSABLE_OBJECT.startTime = resolvedStartTime; + // $FlowExpectedError[cannot-write] MEASURE_OPTIONS_REUSABLE_OBJECT.duration = resolvedDuration; + // $FlowExpectedError[cannot-write] MEASURE_OPTIONS_REUSABLE_OBJECT.detail = resolvedDetail; - const entry = new PerformanceMeasure( - resolvedMeasureName, - MEASURE_OPTIONS_REUSABLE_OBJECT, - ); + const entry = new PerformanceMeasure(MEASURE_OPTIONS_REUSABLE_OBJECT); cachedReportMeasure( resolvedMeasureName, diff --git a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js index f2ddfceda554..e5184c283994 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js @@ -28,24 +28,25 @@ export type PerformanceEntryJSON = { ... }; +export interface PerformanceEntryInit { + +name: string; + +startTime: DOMHighResTimeStamp; + +duration: DOMHighResTimeStamp; +} + export class PerformanceEntry { // We don't use private fields because they're significantly slower to // initialize on construction and to access. // We also need these to be protected so they can be initialized in subclasses // where we avoid calling `super()` for performance reasons. - __name: string; __entryType: PerformanceEntryType; + __name: string; __startTime: DOMHighResTimeStamp; __duration: DOMHighResTimeStamp; - constructor(init: { - name: string, - entryType: PerformanceEntryType, - startTime: DOMHighResTimeStamp, - duration: DOMHighResTimeStamp, - }) { + constructor(entryType: PerformanceEntryType, init: PerformanceEntryInit) { + this.__entryType = entryType; this.__name = init.name; - this.__entryType = init.entryType; this.__startTime = init.startTime; this.__duration = init.duration; } diff --git a/packages/react-native/src/private/webapis/performance/ResourceTiming.js b/packages/react-native/src/private/webapis/performance/ResourceTiming.js index 0f02887250b5..65440e00a8a6 100644 --- a/packages/react-native/src/private/webapis/performance/ResourceTiming.js +++ b/packages/react-native/src/private/webapis/performance/ResourceTiming.js @@ -29,6 +29,19 @@ export type PerformanceResourceTimingJSON = { ... }; +export interface PerformanceResourceTimingInit { + +name: string; + +startTime: DOMHighResTimeStamp; + +duration: DOMHighResTimeStamp; + +fetchStart: DOMHighResTimeStamp; + +requestStart: DOMHighResTimeStamp; + +connectStart: DOMHighResTimeStamp; + +connectEnd: DOMHighResTimeStamp; + +responseStart: DOMHighResTimeStamp; + +responseEnd: DOMHighResTimeStamp; + +responseStatus?: number; +} + export class PerformanceResourceTiming extends PerformanceEntry { #fetchStart: DOMHighResTimeStamp; #requestStart: DOMHighResTimeStamp; @@ -38,24 +51,9 @@ export class PerformanceResourceTiming extends PerformanceEntry { #responseEnd: DOMHighResTimeStamp; #responseStatus: ?number; - constructor(init: { - name: string, - startTime: DOMHighResTimeStamp, - duration: DOMHighResTimeStamp, - fetchStart: DOMHighResTimeStamp, - requestStart: DOMHighResTimeStamp, - connectStart: DOMHighResTimeStamp, - connectEnd: DOMHighResTimeStamp, - responseStart: DOMHighResTimeStamp, - responseEnd: DOMHighResTimeStamp, - responseStatus?: number, - }) { - super({ - name: init.name, - entryType: 'resource', - startTime: init.startTime, - duration: init.duration, - }); + constructor(init: PerformanceResourceTimingInit) { + super('resource', init); + this.#fetchStart = init.fetchStart; this.#requestStart = init.requestStart; this.#connectStart = init.connectStart; diff --git a/packages/react-native/src/private/webapis/performance/UserTiming.js b/packages/react-native/src/private/webapis/performance/UserTiming.js index c52df24e92b4..910f6128d5d7 100644 --- a/packages/react-native/src/private/webapis/performance/UserTiming.js +++ b/packages/react-native/src/private/webapis/performance/UserTiming.js @@ -9,8 +9,10 @@ */ // flowlint unsafe-getters-setters:off - -import type {DOMHighResTimeStamp} from './PerformanceEntry'; +import type { + DOMHighResTimeStamp, + PerformanceEntryInit, +} from './PerformanceEntry'; import type { ExtensionMarkerPayload, ExtensionTrackEntryPayload, @@ -25,18 +27,16 @@ export type DetailType = // but we'll use it as documentation for how to use the extensibility API. | {devtools?: ExtensionMarkerPayload | ExtensionTrackEntryPayload, ...}; -export type PerformanceMarkOptions = $ReadOnly<{ - detail?: DetailType, - startTime?: DOMHighResTimeStamp, -}>; +export interface PerformanceMarkOptions { + +detail?: DetailType; + +startTime?: DOMHighResTimeStamp; +} export type TimeStampOrName = DOMHighResTimeStamp | string; -export type PerformanceMeasureInit = $ReadOnly<{ - detail?: DetailType, - startTime: DOMHighResTimeStamp, - duration: DOMHighResTimeStamp, -}>; +export interface PerformanceMeasureInit extends PerformanceEntryInit { + +detail?: DetailType; +} class PerformanceMarkTemplate extends PerformanceEntry { // We don't use private fields because they're significantly slower to @@ -45,9 +45,8 @@ class PerformanceMarkTemplate extends PerformanceEntry { // This constructor isn't really used. See `PerformanceMark` below. constructor(markName: string, markOptions?: PerformanceMarkOptions) { - super({ + super('mark', { name: markName, - entryType: 'mark', startTime: markOptions?.startTime ?? getCurrentTimeStamp(), duration: 0, }); @@ -72,8 +71,8 @@ export const PerformanceMark: typeof PerformanceMarkTemplate = markName: string, markOptions?: PerformanceMarkOptions, ) { - this.__name = markName; this.__entryType = 'mark'; + this.__name = markName; this.__startTime = markOptions?.startTime ?? getCurrentTimeStamp(); this.__duration = 0; @@ -89,15 +88,10 @@ class PerformanceMeasureTemplate extends PerformanceEntry { __detail: DetailType; // This constructor isn't really used. See `PerformanceMeasure` below. - constructor(measureName: string, measureOptions: PerformanceMeasureInit) { - super({ - name: measureName, - entryType: 'measure', - startTime: measureOptions.startTime, - duration: measureOptions.duration, - }); + constructor(init: PerformanceMeasureInit) { + super('measure', init); - this.__detail = measureOptions?.detail ?? null; + this.__detail = init?.detail ?? null; } get detail(): DetailType { @@ -110,15 +104,14 @@ export const PerformanceMeasure: typeof PerformanceMeasureTemplate = // $FlowExpectedError[incompatible-type] function PerformanceMeasure( this: PerformanceMeasureTemplate, - measureName: string, - measureOptions: PerformanceMeasureInit, + init: PerformanceMeasureInit, ) { - this.__name = measureName; this.__entryType = 'measure'; - this.__startTime = measureOptions.startTime; - this.__duration = measureOptions.duration; + this.__name = init.name; + this.__startTime = init.startTime; + this.__duration = init.duration; - this.__detail = measureOptions.detail ?? null; + this.__detail = init.detail ?? null; }; // $FlowExpectedError[prop-missing] diff --git a/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js b/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js index 900541b4bba8..40854b834ba7 100644 --- a/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js @@ -44,7 +44,6 @@ export function rawToPerformanceEntry( case RawPerformanceEntryTypeValues.LONGTASK: return new PerformanceLongTaskTiming({ name: entry.name, - entryType: rawToPerformanceEntryType(entry.entryType), startTime: entry.startTime, duration: entry.duration, }); @@ -53,7 +52,8 @@ export function rawToPerformanceEntry( startTime: entry.startTime, }); case RawPerformanceEntryTypeValues.MEASURE: - return new PerformanceMeasure(entry.name, { + return new PerformanceMeasure({ + name: entry.name, startTime: entry.startTime, duration: entry.duration, }); @@ -71,9 +71,8 @@ export function rawToPerformanceEntry( responseStatus: entry.responseStatus, }); default: - return new PerformanceEntry({ + return new PerformanceEntry(rawToPerformanceEntryType(entry.entryType), { name: entry.name, - entryType: rawToPerformanceEntryType(entry.entryType), startTime: entry.startTime, duration: entry.duration, }); From 1716b3ca5cef9badd7d382ab3af1cb087dfb8189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Sep 2025 09:18:19 -0700 Subject: [PATCH 0007/1110] Implement private constructors for Performance APIs (#53430) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53430 Changelog: [internal] This fixes the spec-compliance of several classes in the Performance API by not allowing userland code to instantiate them directly. This also exposes some missing interfaces from the Performance API in the global scope. Reviewed By: rshest Differential Revision: D80800076 fbshipit-source-id: f6439b9c7914817ef552e78fd61646ccab1e1de2 --- .../private/setup/setUpPerformanceObserver.js | 52 +++++++++++++------ .../webapis/performance/EventTiming.js | 24 +++++++++ .../private/webapis/performance/LongTasks.js | 25 +++++++++ .../webapis/performance/Performance.js | 12 +++++ .../webapis/performance/PerformanceEntry.js | 12 +++++ .../performance/PerformanceObserver.js | 13 +++++ .../webapis/performance/ResourceTiming.js | 13 +++++ .../private/webapis/performance/UserTiming.js | 12 +++++ .../__tests__/EventTimingAPI-itest.js | 15 ++++++ .../__tests__/LongTasksAPI-itest.js | 16 ++++++ .../__tests__/Performance-itest.js | 29 +++++++++++ .../__tests__/PerformanceObserver-itest.js | 10 ++++ .../performance/__tests__/UserTiming-itest.js | 18 +++++++ 13 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js diff --git a/packages/react-native/src/private/setup/setUpPerformanceObserver.js b/packages/react-native/src/private/setup/setUpPerformanceObserver.js index feeb24acff2f..937e68fb37b7 100644 --- a/packages/react-native/src/private/setup/setUpPerformanceObserver.js +++ b/packages/react-native/src/private/setup/setUpPerformanceObserver.js @@ -20,21 +20,34 @@ export default function setUpPerformanceObserver() { initialized = true; polyfillGlobal( - 'PerformanceObserver', + 'EventCounts', + () => require('../webapis/performance/EventTiming').EventCounts_public, + ); + + polyfillGlobal( + 'Performance', + () => require('../webapis/performance/Performance').Performance_public, + ); + + polyfillGlobal( + 'PerformanceEntry', () => - require('../webapis/performance/PerformanceObserver').PerformanceObserver, + require('../webapis/performance/PerformanceEntry') + .PerformanceEntry_public, ); polyfillGlobal( - 'PerformanceObserverEntryList', + 'PerformanceEventTiming', () => - require('../webapis/performance/PerformanceObserver') - .PerformanceObserverEntryList, + require('../webapis/performance/EventTiming') + .PerformanceEventTiming_public, ); polyfillGlobal( - 'PerformanceEntry', - () => require('../webapis/performance/PerformanceEntry').PerformanceEntry, + 'PerformanceLongTaskTiming', + () => + require('../webapis/performance/LongTasks') + .PerformanceLongTaskTiming_public, ); polyfillGlobal( @@ -44,28 +57,33 @@ export default function setUpPerformanceObserver() { polyfillGlobal( 'PerformanceMeasure', - () => require('../webapis/performance/UserTiming').PerformanceMeasure, + () => + require('../webapis/performance/UserTiming').PerformanceMeasure_public, ); polyfillGlobal( - 'PerformanceEventTiming', - () => require('../webapis/performance/EventTiming').PerformanceEventTiming, + 'PerformanceObserver', + () => + require('../webapis/performance/PerformanceObserver').PerformanceObserver, ); polyfillGlobal( - 'PerformanceResourceTiming', + 'PerformanceObserverEntryList', () => - require('../webapis/performance/ResourceTiming') - .PerformanceResourceTiming, + require('../webapis/performance/PerformanceObserver') + .PerformanceObserverEntryList_public, ); polyfillGlobal( - 'TaskAttributionTiming', - () => require('../webapis/performance/LongTasks').TaskAttributionTiming, + 'PerformanceResourceTiming', + () => + require('../webapis/performance/ResourceTiming') + .PerformanceResourceTiming_public, ); polyfillGlobal( - 'PerformanceLongTaskTiming', - () => require('../webapis/performance/LongTasks').PerformanceLongTaskTiming, + 'TaskAttributionTiming', + () => + require('../webapis/performance/LongTasks').TaskAttributionTiming_public, ); } diff --git a/packages/react-native/src/private/webapis/performance/EventTiming.js b/packages/react-native/src/private/webapis/performance/EventTiming.js index 95657a3b0427..0748ef6f6b23 100644 --- a/packages/react-native/src/private/webapis/performance/EventTiming.js +++ b/packages/react-native/src/private/webapis/performance/EventTiming.js @@ -70,6 +70,18 @@ export class PerformanceEventTiming extends PerformanceEntry { } } +export const PerformanceEventTiming_public: typeof PerformanceEventTiming = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function PerformanceEventTiming() { + throw new TypeError( + "Failed to construct 'PerformanceEventTiming': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +PerformanceEventTiming_public.prototype = PerformanceEventTiming.prototype; + type EventCountsForEachCallbackType = | (() => void) | ((value: number) => void) @@ -134,3 +146,15 @@ export class EventCounts { return getCachedEventCounts().values(); } } + +export const EventCounts_public: typeof EventCounts = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function EventCounts() { + throw new TypeError( + "Failed to construct 'EventCounts': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +EventCounts_public.prototype = EventCounts.prototype; diff --git a/packages/react-native/src/private/webapis/performance/LongTasks.js b/packages/react-native/src/private/webapis/performance/LongTasks.js index 9c27419dd072..d5267c89df76 100644 --- a/packages/react-native/src/private/webapis/performance/LongTasks.js +++ b/packages/react-native/src/private/webapis/performance/LongTasks.js @@ -24,6 +24,18 @@ export type PerformanceLongTaskTimingJSON = { export class TaskAttributionTiming extends PerformanceEntry {} +export const TaskAttributionTiming_public: typeof TaskAttributionTiming = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function TaskAttributionTiming() { + throw new TypeError( + "Failed to construct 'TaskAttributionTiming': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +TaskAttributionTiming_public.prototype = TaskAttributionTiming.prototype; + const EMPTY_ATTRIBUTION: $ReadOnlyArray = Object.preventExtensions([]); @@ -45,3 +57,16 @@ export class PerformanceLongTaskTiming extends PerformanceEntry { }; } } + +export const PerformanceLongTaskTiming_public: typeof PerformanceLongTaskTiming = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function PerformanceLongTaskTiming() { + throw new TypeError( + "Failed to construct 'PerformanceLongTaskTiming': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +PerformanceLongTaskTiming_public.prototype = + PerformanceLongTaskTiming.prototype; diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 6cd2fd5b0a74..86c380111ba3 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -443,4 +443,16 @@ export default class Performance { } } +export const Performance_public: typeof Performance = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function Performance() { + throw new TypeError( + "Failed to construct 'Performance': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +Performance_public.prototype = Performance.prototype; + setPlatformObject(Performance); diff --git a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js index e5184c283994..03e4b6789562 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js @@ -77,6 +77,18 @@ export class PerformanceEntry { } } +export const PerformanceEntry_public: typeof PerformanceEntry = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function PerformanceEntry() { + throw new TypeError( + "Failed to construct 'PerformanceEntry': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +PerformanceEntry_public.prototype = PerformanceEntry.prototype; + setPlatformObject(PerformanceEntry); export type PerformanceEntryList = $ReadOnlyArray; diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index e2efde682ea2..375c49a42d45 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -57,6 +57,19 @@ export class PerformanceObserverEntryList { } } +export const PerformanceObserverEntryList_public: typeof PerformanceObserverEntryList = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function PerformanceObserverEntryList() { + throw new TypeError( + "Failed to construct 'PerformanceObserverEntryList': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +PerformanceObserverEntryList_public.prototype = + PerformanceObserverEntryList.prototype; + export type PerformanceObserverCallbackOptions = { droppedEntriesCount: number, }; diff --git a/packages/react-native/src/private/webapis/performance/ResourceTiming.js b/packages/react-native/src/private/webapis/performance/ResourceTiming.js index 65440e00a8a6..8e1895d0934d 100644 --- a/packages/react-native/src/private/webapis/performance/ResourceTiming.js +++ b/packages/react-native/src/private/webapis/performance/ResourceTiming.js @@ -104,3 +104,16 @@ export class PerformanceResourceTiming extends PerformanceEntry { }; } } + +export const PerformanceResourceTiming_public: typeof PerformanceResourceTiming = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function PerformanceResourceTiming() { + throw new TypeError( + "Failed to construct 'PerformanceResourceTiming': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +PerformanceResourceTiming_public.prototype = + PerformanceResourceTiming.prototype; diff --git a/packages/react-native/src/private/webapis/performance/UserTiming.js b/packages/react-native/src/private/webapis/performance/UserTiming.js index 910f6128d5d7..5675fcafd6ec 100644 --- a/packages/react-native/src/private/webapis/performance/UserTiming.js +++ b/packages/react-native/src/private/webapis/performance/UserTiming.js @@ -116,3 +116,15 @@ export const PerformanceMeasure: typeof PerformanceMeasureTemplate = // $FlowExpectedError[prop-missing] PerformanceMeasure.prototype = PerformanceMeasureTemplate.prototype; + +export const PerformanceMeasure_public: typeof PerformanceMeasure = + /* eslint-disable no-shadow */ + // $FlowExpectedError[incompatible-type] + function PerformanceMeasure() { + throw new TypeError( + "Failed to construct 'PerformanceMeasure': Illegal constructor", + ); + }; + +// $FlowExpectedError[prop-missing] +PerformanceMeasure_public.prototype = PerformanceMeasure.prototype; diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js index 886a5da84811..e037b0ab6f2c 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js @@ -244,6 +244,21 @@ describe('Event Timing API', () => { expect([...performance.eventCounts.values()]).toEqual([1, 1, 3]); }); + it('does NOT allow creating instances of PerformanceEventTiming directly', () => { + expect(() => { + return new PerformanceEventTiming(); + }).toThrow( + "Failed to construct 'PerformanceEventTiming': Illegal constructor", + ); + }); + + it('does NOT allow creating instances of EventCounts directly', () => { + expect(() => { + // $FlowExpectedError[cannot-resolve-name] + return new EventCounts(); + }).toThrow("Failed to construct 'EventCounts': Illegal constructor"); + }); + describe('durationThreshold option', () => { it('works when used with `type`', () => { const callback = jest.fn(); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js index 990663ff03b2..6f2a06f2a9c3 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js @@ -191,4 +191,20 @@ describe('LongTasks API', () => { expect(entry.attribution).toEqual([]); }); }); + + it('does NOT allow creating instances of PerformanceLongTaskTiming directly', () => { + expect(() => { + return new PerformanceLongTaskTiming(); + }).toThrow( + "Failed to construct 'PerformanceLongTaskTiming': Illegal constructor", + ); + }); + + it('does NOT allow creating instances of TaskAttributionTiming directly', () => { + expect(() => { + return new TaskAttributionTiming(); + }).toThrow( + "Failed to construct 'TaskAttributionTiming': Illegal constructor", + ); + }); }); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js new file mode 100644 index 000000000000..32db3055f9c3 --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; + +setUpPerformanceObserver(); + +describe('Performance', () => { + it('does NOT allow creating instances of Performance directly', () => { + expect(() => { + return new Performance(); + }).toThrow("Failed to construct 'Performance': Illegal constructor"); + }); + + it('does NOT allow creating instances of PerformanceEntry directly', () => { + expect(() => { + return new PerformanceEntry(); + }).toThrow("Failed to construct 'PerformanceEntry': Illegal constructor"); + }); +}); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js index 70e91786f959..ad3d38af9309 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js @@ -11,6 +11,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; +import {PerformanceObserverEntryList_public} from '../PerformanceObserver'; import * as Fantom from '@react-native/fantom'; setUpPerformanceObserver(); @@ -117,4 +118,13 @@ describe('PerformanceObserver', () => { expect(callback).not.toHaveBeenCalled(); }); }); + + it('does NOT allow creating instances of PerformanceObserverEntryList directly', () => { + expect(() => { + // $FlowExpectedError[incompatible-type] + return new PerformanceObserverEntryList_public(); + }).toThrow( + "Failed to construct 'PerformanceObserverEntryList': Illegal constructor", + ); + }); }); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js index 00714de389fa..eb54e986f6ee 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js @@ -44,6 +44,24 @@ describe('User Timing', () => { mockClock.uninstall(); }); + it('allows creating instances of PerformanceMark directly', () => { + const before = performance.now(); + const entry = new PerformanceMark('mark-now'); + const after = performance.now(); + + expect(entry).toBeInstanceOf(PerformanceMark); + expect(entry.startTime).toBeGreaterThanOrEqual(before); + expect(entry.startTime).toBeLessThanOrEqual(after); + expect(entry.duration).toBe(0); + expect(entry.detail).toBe(null); + }); + + it('does NOT allow creating instances of PerformanceMeasure directly', () => { + expect(() => { + return new PerformanceMeasure(); + }).toThrow("Failed to construct 'PerformanceMeasure': Illegal constructor"); + }); + describe('mark', () => { it('works with default timestamp', () => { mockClock.setTime(25); From 727caca09c27da02371ffc4568ffb3a3b834ce82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 1 Sep 2025 09:18:19 -0700 Subject: [PATCH 0008/1110] Set up modern performance APIs if the native module is available (#53431) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53431 Changelog: [internal] This renames `setUpPerformanceObserver` as `setUpPerformanceModern` and removes the need to call it manually. If the native module is defined, we define the whole new API. Reviewed By: javache Differential Revision: D80803626 fbshipit-source-id: ef41cb9aa959ee898d32724c102d7597e6bee84e --- packages/react-native/Libraries/Core/setUpPerformance.js | 8 +++----- ...pPerformanceObserver.js => setUpPerformanceModern.js} | 9 ++++++++- .../performance/__tests__/EventTimingAPI-itest.js | 3 --- .../webapis/performance/__tests__/LongTasksAPI-itest.js | 3 --- .../webapis/performance/__tests__/Performance-itest.js | 4 ---- .../performance/__tests__/PerformanceObserver-itest.js | 3 --- .../webapis/performance/__tests__/UserTiming-itest.js | 3 --- 7 files changed, 11 insertions(+), 22 deletions(-) rename packages/react-native/src/private/setup/{setUpPerformanceObserver.js => setUpPerformanceModern.js} (85%) diff --git a/packages/react-native/Libraries/Core/setUpPerformance.js b/packages/react-native/Libraries/Core/setUpPerformance.js index edb00ce81e21..7aa5945d6f55 100644 --- a/packages/react-native/Libraries/Core/setUpPerformance.js +++ b/packages/react-native/Libraries/Core/setUpPerformance.js @@ -4,19 +4,17 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ +import setUpPerformanceModern from '../../src/private/setup/setUpPerformanceModern'; import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance'; // In case if the native implementation of the Performance API is available, use it, // otherwise fall back to the legacy/default one, which only defines 'Performance.now()' if (NativePerformance) { - const Performance = - require('../../src/private/webapis/performance/Performance').default; - // $FlowExpectedError[cannot-write] - global.performance = new Performance(); + setUpPerformanceModern(); } else { if (!global.performance) { // $FlowExpectedError[cannot-write] diff --git a/packages/react-native/src/private/setup/setUpPerformanceObserver.js b/packages/react-native/src/private/setup/setUpPerformanceModern.js similarity index 85% rename from packages/react-native/src/private/setup/setUpPerformanceObserver.js rename to packages/react-native/src/private/setup/setUpPerformanceModern.js index 937e68fb37b7..22a7ca135043 100644 --- a/packages/react-native/src/private/setup/setUpPerformanceObserver.js +++ b/packages/react-native/src/private/setup/setUpPerformanceModern.js @@ -12,13 +12,20 @@ import {polyfillGlobal} from '../../../Libraries/Utilities/PolyfillFunctions'; let initialized = false; -export default function setUpPerformanceObserver() { +export default function setUpPerformanceModern() { if (initialized) { return; } initialized = true; + const Performance = require('../webapis/performance/Performance').default; + + // We don't use `polyfillGlobal` to define this lazily because the + // `performance` object is always accessed. + // $FlowExpectedError[cannot-write] + global.performance = new Performance(); + polyfillGlobal( 'EventCounts', () => require('../webapis/performance/EventTiming').EventCounts_public, diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js index e037b0ab6f2c..8a5d7bbade83 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js @@ -15,12 +15,9 @@ import * as Fantom from '@react-native/fantom'; import nullthrows from 'nullthrows'; import {useState} from 'react'; import {Text, View} from 'react-native'; -import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver'; const NativePerformance = nullthrows(MaybeNativePerformance); -setUpPerformanceObserver(); - function sleep(ms: number) { const end = performance.now() + ms; while (performance.now() < end) {} diff --git a/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js index 6f2a06f2a9c3..e3b8443676e7 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/LongTasksAPI-itest.js @@ -13,9 +13,6 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import type {PerformanceObserverCallbackOptions} from '../PerformanceObserver'; import * as Fantom from '@react-native/fantom'; -import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver'; - -setUpPerformanceObserver(); function ensurePerformanceLongTaskTiming( value: mixed, diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js index 32db3055f9c3..4c8c5447744e 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js @@ -10,10 +10,6 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; - -setUpPerformanceObserver(); - describe('Performance', () => { it('does NOT allow creating instances of Performance directly', () => { expect(() => { diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js index ad3d38af9309..41ddaa4be8f3 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-itest.js @@ -10,12 +10,9 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; -import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; import {PerformanceObserverEntryList_public} from '../PerformanceObserver'; import * as Fantom from '@react-native/fantom'; -setUpPerformanceObserver(); - describe('PerformanceObserver', () => { it('receives notifications for marks and measures', () => { const callback = jest.fn(); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js index eb54e986f6ee..99cb13114d4f 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js @@ -11,12 +11,9 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import ensureInstance from '../../../__tests__/utilities/ensureInstance'; -import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver'; import DOMException from '../../errors/DOMException'; import * as Fantom from '@react-native/fantom'; -setUpPerformanceObserver(); - function getThrownError(fn: () => mixed): mixed { try { fn(); From 9731e8ebc5ea87526a91b9903172639e062cd920 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Mon, 1 Sep 2025 13:32:10 -0700 Subject: [PATCH 0009/1110] Replace `execSync` with `spawnSync` for tarball extraction paths that need to be escaped (#53540) Summary: Follow-up to https://github.com/facebook/react-native/issues/53194 This wasn't previously visible in testing without prebuilds and without a release build. This doesn't show up in debug builds. When testing more against paths that contain spaces, I noticed that release builds can still run into trouble due to the use of `execSync` without escaping paths. While, in other scripts that aren't used in user-projects (afaict), we often escape with quotes and rely on `execSync` calling the shell (due to its `shell: true` default), in some scripts we don't have quote escapes. That said, since paths could in theory contain quotes, adding quotes wouldn't be sufficient. Instead, since the affected `tar` calls are really trivial, we can instead use `spawnSync` with the `shell: false` default, which escapes arguments automatically. ## Changelog: [IOS] [FIXED] - fix Node scripts related to prebuilt tarball extraction for paths containing whitespaces Pull Request resolved: https://github.com/facebook/react-native/pull/53540 Test Plan: - Create a project in a folder `with spaces` and build a release build Reviewed By: cipolleschi, cortinico Differential Revision: D81406841 Pulled By: robhogan fbshipit-source-id: 08bb06b2cd2b15dc17c2f95fab9024129deca6f3 --- packages/react-native/scripts/replace-rncore-version.js | 6 ++++-- .../sdks/hermes-engine/utils/replace_hermes_version.js | 6 ++++-- .../third-party-podspecs/replace_dependencies_version.js | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/react-native/scripts/replace-rncore-version.js b/packages/react-native/scripts/replace-rncore-version.js index 16c2d7621505..1385b71ffb4a 100644 --- a/packages/react-native/scripts/replace-rncore-version.js +++ b/packages/react-native/scripts/replace-rncore-version.js @@ -10,7 +10,7 @@ 'use strict'; -const {execSync} = require('child_process'); +const {spawnSync} = require('child_process'); const fs = require('fs'); const yargs = require('yargs'); @@ -67,7 +67,9 @@ function replaceRNCoreConfiguration( fs.mkdirSync(finalLocation, {recursive: true}); console.log('Extracting the tarball', tarballURLPath); - execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`); + spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { + stdio: 'inherit', + }); } function updateLastBuildConfiguration(configuration /*: string */) { diff --git a/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js b/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js index 3dc996471318..18ef3877b563 100644 --- a/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js +++ b/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js @@ -10,7 +10,7 @@ 'use strict'; -const {execSync} = require('child_process'); +const {spawnSync} = require('child_process'); const fs = require('fs'); const yargs = require('yargs'); @@ -62,7 +62,9 @@ function replaceHermesConfiguration(configuration, version, podsRoot) { fs.mkdirSync(finalLocation, {recursive: true}); console.log('Extracting the tarball'); - execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`); + spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { + stdio: 'inherit', + }); } function updateLastBuildConfiguration(configuration) { diff --git a/packages/react-native/third-party-podspecs/replace_dependencies_version.js b/packages/react-native/third-party-podspecs/replace_dependencies_version.js index 56d0b9f2706b..66fa888758eb 100644 --- a/packages/react-native/third-party-podspecs/replace_dependencies_version.js +++ b/packages/react-native/third-party-podspecs/replace_dependencies_version.js @@ -10,7 +10,7 @@ 'use strict'; -const {execSync} = require('child_process'); +const {spawnSync} = require('child_process'); const fs = require('fs'); const yargs = require('yargs'); @@ -66,7 +66,9 @@ function replaceRNDepsConfiguration( fs.mkdirSync(finalLocation, {recursive: true}); console.log('Extracting the tarball', tarballURLPath); - execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`); + spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { + stdio: 'inherit', + }); // Now we need to remove the extra third-party folder as we do in the podspec's prepare-script // We need to take the ReactNativeDependencies.xcframework folder and move it up one level From 6e47c953d3a3e7e53e8708f41dfb120461357a21 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Tue, 2 Sep 2025 03:01:57 -0700 Subject: [PATCH 0010/1110] fix release script testing artifact for rntester (#53552) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53552 Changelog: [Internal] Reviewed By: fabriziocucci, cortinico, hoxyq Differential Revision: D81452967 fbshipit-source-id: 3032b49b6c7fd49901b8f47886084c98479b368f --- scripts/release-testing/utils/github-actions-utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/release-testing/utils/github-actions-utils.js b/scripts/release-testing/utils/github-actions-utils.js index 92574b06fbfa..fefa287ebc74 100644 --- a/scripts/release-testing/utils/github-actions-utils.js +++ b/scripts/release-testing/utils/github-actions-utils.js @@ -210,7 +210,7 @@ async function artifactURLForRNTesterAPK( } async function artifactURLForHermesRNTesterApp() /*: Promise */ { - return getArtifactURL('RNTesterApp-NewArch-Hermes-Debug'); + return getArtifactURL('RNTesterApp-NewArch-Debug'); } async function artifactURLForMavenLocal() /*: Promise */ { @@ -223,7 +223,9 @@ async function getArtifactURL( const filteredUrls = artifacts.artifacts.filter(a => a.name === artifactName); if (filteredUrls.length === 0) { - console.error(`No artifact found with name ${artifactName}`); + console.error( + `No artifact found with name ${artifactName}.\n\nAvailable artifacts: ${artifacts.artifacts.map(a => a.name).join(' ')}`, + ); process.exit(1); } return filteredUrls[0].archive_download_url; From 8f0713fd4bfdb8ece5616c9aa47a20ee381b792e Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 2 Sep 2025 03:25:54 -0700 Subject: [PATCH 0011/1110] Add test for empty layout culling skip (#53551) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53551 See title. Follow up on D81044841 Changelog: [Internal] Reviewed By: rshest Differential Revision: D81447133 fbshipit-source-id: 7e6ca4523401c30c4861606c795f306193d97a15 --- .../__tests__/ScrollView-viewCulling-itest.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js index c903094e35f8..36299230016e 100644 --- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js +++ b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js @@ -2607,3 +2607,35 @@ describe('horizontal ScrollView in RTL script', () => { ]); }); }); + +describe('Views with no layout', () => { + it('are not culled', () => { + const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100}); + + Fantom.runTask(() => { + root.render( + + + + + + , + ); + }); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "RootView", nativeID: (root)}', + 'Create {type: "ScrollView", nativeID: (N/A)}', + 'Create {type: "View", nativeID: (N/A)}', + 'Create {type: "View", nativeID: "viewWithLayout"}', + 'Create {type: "View", nativeID: "viewWithoutLayout"}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "viewWithLayout"}', + 'Insert {type: "View", parentNativeID: (N/A), index: 1, nativeID: "viewWithoutLayout"}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', + 'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}', + ]); + }); +}); From 5128d35e69c111c8f980f7b4823021dc17de9f59 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Tue, 2 Sep 2025 06:11:26 -0700 Subject: [PATCH 0012/1110] changelog/v0.82.0-rc.0 (#53562) Summary: Changelog: [Internal] changelog for v0.82.0-rc.0 Pull Request resolved: https://github.com/facebook/react-native/pull/53562 Reviewed By: fabriziocucci, cortinico Differential Revision: D81483668 Pulled By: vzaidman fbshipit-source-id: 6f044ce9918f147d981f87c9988e27600cac0ab7 --- CHANGELOG.md | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d79913fa8142..bada6155afa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,186 @@ # Changelog +## v0.82.0-rc.0 + +### Breaking + +- **Appearance.setColorScheme:** `Appearance.setColorScheme` no longer accepts a nullable value ([a4581ecd8b](https://github.com/facebook/react-native/commit/a4581ecd8b6df5efa44dfe6d43708320209c900b) by [@huntie](https://github.com/huntie)) +- **`CxxSharedModuleWrapper`:** Removed CxxSharedModuleWrapper ([fafbee2402](https://github.com/facebook/react-native/commit/fafbee240235ea0e63eb01abd31ce32d6a576429) by [@javache](https://github.com/javache)) +- **DOM API:** Enable DOM APIs in host component refs ([2ad845ccb2](https://github.com/facebook/react-native/commit/2ad845ccb2fea277e05513dcf41407026a8224f0) by [@rubennorte](https://github.com/rubennorte)) +- **Error Handling:** Unhandled promises are now handled by ExceptionsManager.handleException, instead of being swallowed as Logbox Warnings. ([c4082c9ce2](https://github.com/facebook/react-native/commit/c4082c9ce208a324c2d011823ca2ba432411aafc) by [@krystofwoldrich](https://github.com/krystofwoldrich)) +- **`shouldEmitW3CPointerEvents`:** Migrate `shouldPressibilityUseW3CPointerEventsForHover` to common private feature flags and remove `shouldEmitW3CPointerEvents` flag. ([fb4587780e](https://github.com/facebook/react-native/commit/fb4587780e8d6111139d73598a9a26ff392dee28) by [@coado](https://github.com/coado)) +- **TurboModuleUtils:** Remove unused ReactCommon/TurboModuleUtils functions #deepCopyJSIObject and #deepCopyJSIArray ([ead669ade3](https://github.com/facebook/react-native/commit/ead669ade31ee703c407f96c0ce98d8f2991bdc8) by [@christophpurrer](https://github.com/christophpurrer)) + +#### Android specific + +- **Deps:** Gradle to 9.0 ([7f93b664b4](https://github.com/facebook/react-native/commit/7f93b664b41ba11226aae7cca0e7c9b7f38a7d18) by [@cortinico](https://github.com/cortinico)) +- **Image Prefetching:** Android: Image Prefetching send ImageResizeMode as enum value ([e30f34eda6](https://github.com/facebook/react-native/commit/e30f34eda689994cab8cd62aa38175238da8638b) by [@christophpurrer](https://github.com/christophpurrer)) +- **New Architecture:** Remove possibility to newArchEnabled=false in 0.82 ([d5d21d0614](https://github.com/facebook/react-native/commit/d5d21d061493ee973c789a7c6ab8cceebc1f04f9) by [@cortinico](https://github.com/cortinico)) +- **`reactNativeHost`:** Throw Exception if ReactApplication.reactNativeHost is not overriden ([0d3791ca0a](https://github.com/facebook/react-native/commit/0d3791ca0ab30d5a12881c9901f31291b3e998c6) by [@mdvacca](https://github.com/mdvacca)) +- **ViewManagerInterfaces:** Migrate ViewManagerInterfaces to kotlin. Some types in code generated ViewManagerInterfaces might differ. e.g. this will start enforcing nullability in parameters of viewManagerInterface methods (e.g. String commands parameters are not nullable, view params are not nullable in any method, etc) ([79ca9036d3](https://github.com/facebook/react-native/commit/79ca9036d39c16cd115dc0427cb7092f358ac47e) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS Specific +- **New Architecture:** Removed the opt-out from the New Architecture. ([83e6eaf693](https://github.com/facebook/react-native/commit/83e6eaf693f967b7870a5d4896cbb799206a14f0) by [@cipolleschi](https://github.com/cipolleschi)) + +### Added + +- **Animated:** `Animated.CompositeAnomation` is now exposed when using `"react-native-strict-api"` ([024d25794a](https://github.com/facebook/react-native/commit/024d25794a51c94c877c1dfa115a82ebbf559614) by [@huntie](https://github.com/huntie)) +- **Animated:** Allow calling createAnimatedNode without batching ([d9d9a49e18](https://github.com/facebook/react-native/commit/d9d9a49e18f3c51caa18cf7da0a1fcd62f1ecf18) by [@zeyap](https://github.com/zeyap)) +- **Animated:** Allow filter usage with native animated driver. ([138d0eb01d](https://github.com/facebook/react-native/commit/138d0eb01dbe597261459a37d364d1780c3ef228) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) +- **API:** Expose NativeComponentRegistry API as JavaScript root export ([f936780cd5](https://github.com/facebook/react-native/commit/f936780cd5c0c17797f9d2bbc8f5cee81c2eefce) by [@zhongwuzw](https://github.com/zhongwuzw)) +- **API:** Expose `ReactNativeVersion` API as JavaScript root export ([ec5638abd0](https://github.com/facebook/react-native/commit/ec5638abd0e872be62b6ea5d8df9bed6335c2191) by [@huntie](https://github.com/huntie)) +- **Codegen:** Added getDebugProps to codegen ([e547f466ee](https://github.com/facebook/react-native/commit/e547f466ee41415a75ec6b6f910171285ee7bfc3) by [@cipolleschi](https://github.com/cipolleschi)) +- **Pressable:** Allow setting `blockNativeResponder` on Pressable ([6e4d23ded2](https://github.com/facebook/react-native/commit/6e4d23ded2da4a717bafcc032e3d7a0a5fbe3731) by [@zeyap](https://github.com/zeyap)) +- **Yoga/API:** Make yoga/Yoga.h an umbrell header ([8ed2cee80e](https://github.com/facebook/react-native/commit/8ed2cee80e0aaac2f2a6a897ba450888f274a5a4) by [@rudybear](https://github.com/rudybear)) + +#### Android specific + +- **Build Type:** Create a `debugOptimized` `buildType` for Android ([eb2461c7c9](https://github.com/facebook/react-native/commit/eb2461c7c902ebed272bd2d22d6cff4d3c586da6) by [@cortinico](https://github.com/cortinico)) +- **DevMenu:** Add long-press back as an option to open the DevMenu for devices that lack menu & fast-forward. ([32d37f03ad](https://github.com/facebook/react-native/commit/32d37f03ad05290205a4f04d756f6e1880c4ff89) by [@sbuggay](https://github.com/sbuggay)) +- **DevTools:** `DevSupportManager::openDebugger` now supports an optional `panel` param determining the starting panel ([7eb3536728](https://github.com/facebook/react-native/commit/7eb3536728c4a20f7e51245f4f7b64aa505bd799) by [@huntie](https://github.com/huntie)) +- **DevTools:** Adds a landing view parameter to opening RNDT, enabling arbitrary view focus on launch. ([635c707eec](https://github.com/facebook/react-native/commit/635c707eec18f6d2ceceac2dcee9f458f17f8aab) by [@sbuggay](https://github.com/sbuggay)) +- **HWInput:** Channel up/down hardware events. ([c2a3e4420e](https://github.com/facebook/react-native/commit/c2a3e4420e07147f9a040a665da98dbe22b87a2a) by [@sbuggay](https://github.com/sbuggay)) +- **Manifest:** Add support to specify a single Manifest rather than 2 (main/debug) by using the `usesCleartextTraffic` manifest placeholder which is autoconfigured by RNGP. ([d89acc1596](https://github.com/facebook/react-native/commit/d89acc1596345534882938d2bbf40275a6cb89bd) by [@cortinico](https://github.com/cortinico)) + +#### iOS specific + +- **API:** Add deprecation message for RCTAppdelegate APIs ([d503ea4efc](https://github.com/facebook/react-native/commit/d503ea4efc84b6511cef2a46421a16e044862e88) by [@cipolleschi](https://github.com/cipolleschi)) +- **New Architecture:** Add warning if RCT_NEW_ARCH_ENABLED is set to 0 ([7d0bef2f25](https://github.com/facebook/react-native/commit/7d0bef2f25a206d917e7f5cc2b9a6c088f13a832) by [@cipolleschi](https://github.com/cipolleschi)) + +### Changed + +- **Font:** Enabled `enableFontScaleChangesUpdatingLayout` feature flag by default ([686d14f1d1](https://github.com/facebook/react-native/commit/686d14f1d16c2f02720104ddd395f7d27c908350) by [@j-piasecki](https://github.com/j-piasecki)) +- **Hermes:** Changed names of hermes binaries ([776fca1e7c](https://github.com/facebook/react-native/commit/776fca1e7c978a2d8f817d042836073e4dcb4e0e) by [@j-piasecki](https://github.com/j-piasecki)) +- **Metro:** Bump Metro to ^0.83.1 ([840fd6c83f](https://github.com/facebook/react-native/commit/840fd6c83f45326a796bf2823f8c2fa942aed06c) by [@robhogan](https://github.com/robhogan)) +- **React:** Bumped React to 19.1.1 ([ec5a98b1f5](https://github.com/facebook/react-native/commit/ec5a98b1f5c2137f5f6ff5f5f6706f20384c44df) by [@cipolleschi](https://github.com/cipolleschi)) +- **Runtime:** CDP backend now accepts `addBinding` and `removeBinding` methods earlier, before a Runtime exists. ([3271e57c75](https://github.com/facebook/react-native/commit/3271e57c751e7d1193c1e9f7b53e545231511b9d) by [@motiz88](https://github.com/motiz88)) +- **Typing:** Update types for Platform.version ([f6ba2dbf3b](https://github.com/facebook/react-native/commit/f6ba2dbf3b4c85da1a7f9079fd366a41b160fa69) by [@riteshshukla04](https://github.com/riteshshukla04)) +- **UIManager:** Avoid unnecessary copy of view props map in UIManager::updateShadowTree ([5b38bb4745](https://github.com/facebook/react-native/commit/5b38bb47457f853c2c3d5f275facbb9fbc150683) by [@zeyap](https://github.com/zeyap)) + +#### Android specific + +- **AGP:** AGP to 8.12.0 ([742ef3d661](https://github.com/facebook/react-native/commit/742ef3d6615c8c1202e9f683e6127ac97d7a9e23) by [@cortinico](https://github.com/cortinico)) +- **DevSupportManager:** DevSupport `openDebugger()` methods now accept a `panel: String?` param. Frameworks directly implementing `DevSupportManager` will need to adjust call signatures. ([9dba7112cf](https://github.com/facebook/react-native/commit/9dba7112cfd09b02300869a77dba3dca16f49a28) by [@huntie](https://github.com/huntie)) +- **Kotlin:**Migrated TextAttributeProps to Kotlin. You might need to update your property access to use camelCase instead of Hungarian notation. ([fa921b3c7b](https://github.com/facebook/react-native/commit/fa921b3c7b289800a79196468f993a0eb0bf693f) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrated ReactBaseTextShadowNode to Kotlin. You might need to update your property access to use camelCase instead of Hungarian notation. ([8ccfff9a46](https://github.com/facebook/react-native/commit/8ccfff9a46f317fd78f478c8b3f180441535d1ca) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrated com.facebook.react.bridge.Arguments to Kotlin. ([2534aeaddb](https://github.com/facebook/react-native/commit/2534aeaddb0490b69dfaba6b8d316616c7e10a9c) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaConfig` to Kotlin ([4d5caef76b](https://github.com/facebook/react-native/commit/4d5caef76b83eb7e983364ecc81abb6027e5f98e) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaValue` to Kotlin ([4340dcbae8](https://github.com/facebook/react-native/commit/4340dcbae8fc41cde844e805a1ebfc23d23d164f) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaNative` to Kotlin ([bc54a06fcb](https://github.com/facebook/react-native/commit/bc54a06fcb5b5d1efd8996d8568733b657fc1b06) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaConfigFactory` to Kotlin ([33ca53d9db](https://github.com/facebook/react-native/commit/33ca53d9dbe53b92d65f82dbd53a2e9f23efd4f3) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `DoNotStrip` to Kotlin ([35d8086881](https://github.com/facebook/react-native/commit/35d8086881fac643b0ebc0d53aaf7e79b7ccd830) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaLayoutType` to Kotlin ([7e461003c6](https://github.com/facebook/react-native/commit/7e461003c6592c8c539960bd5e8169c48dd27f50) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `LayoutPassReason` to Kotlin ([db2a9c089c](https://github.com/facebook/react-native/commit/db2a9c089cd5802d99e0fc86e4dc0dbf7c888307) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaNodeFactory` to Kotlin ([40afa75a7c](https://github.com/facebook/react-native/commit/40afa75a7c816a5581223c7bcd1b65b8713edf47) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaMeasureOutput` to Kotlin ([453508ada8](https://github.com/facebook/react-native/commit/453508ada837554455733e3ca94440a7143f51b1) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaMeasureFunction` to Kotlin ([05eddd354e](https://github.com/facebook/react-native/commit/05eddd354e2e80ad3c95ed5a2199a59a77317891) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaStyleInputs` to Kotlin ([001736000f](https://github.com/facebook/react-native/commit/001736000f69ce98db86c17707408bbf3f0ae9a5) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaBaselineFunction` to Kotlin ([a2eb3b299d](https://github.com/facebook/react-native/commit/a2eb3b299dddea60c510821b662dfed55b334df7) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **Kotlin:** Migrate `YogaLogger` to Kotlin ([9c9a39b58e](https://github.com/facebook/react-native/commit/9c9a39b58e12bc734c27a5d9306e792b0dcaf927) by [@mateoguzmana](https://github.com/mateoguzmana)) +- **OnBatchCompleteListener:** Make OnBatchCompleteListener interface internal ([046ff8e58b](https://github.com/facebook/react-native/commit/046ff8e58bed5da0f19adc860b327c7248b19f48) by [@cortinico](https://github.com/cortinico)) +- **ReactSurface:** Changed return type of ReactSurfaceImpl.view to ReactSurfaceView to align with parameter recived by ReactSurfaceImpl.attachView() ([41029d8e91](https://github.com/facebook/react-native/commit/41029d8e91492c34c377374b442b31755874618c) by [@mdvacca](https://github.com/mdvacca)) +- **TextAttributeProps:** Deprecate the field `TextAttributeProps.effectiveLineHeight`. This field was public but never used in OSS. ([ede037ade7](https://github.com/facebook/react-native/commit/ede037ade795bd44725f9bd82cace193a74aa68d) by [@cortinico](https://github.com/cortinico)) +- **ViewManagers:** Changed method arguments names for Core ViewManagers to match the names of ViewManagerInterfaces ([e7d9e0d197](https://github.com/facebook/react-native/commit/e7d9e0d1977c136a85b9a78ef36a258631d1e9ba) by [@mdvacca](https://github.com/mdvacca)) + +### Deprecated + +- **StyleSheet:** `StyleSheet.absoluteFillObject` is deprecated in favor of `StyleSheet.absoluteFill` (equivalent). ([83e19813ff](https://github.com/facebook/react-native/commit/83e19813ff5498ab3497d97fe38dba63a5554425) by [@huntie](https://github.com/huntie)) +- Deprecate all the c++ classes not used by interop, or the new architecture. ([9539cd2626](https://github.com/facebook/react-native/commit/9539cd26261aef646379104833c7f719e3d83d02) by [@RSNara](https://github.com/RSNara)) + +#### Android specific + +- **DevMenu:** Remove bridge mode string from React Native Dev Menu title ([1c838f32a9](https://github.com/facebook/react-native/commit/1c838f32a9bcee3867ec0502b344889308302f26) by [@sbuggay](https://github.com/sbuggay)) +- **New Architecture:** DefaultDevSupportManagerFactory.create() method used for Old Arch ([026e22bb8d](https://github.com/facebook/react-native/commit/026e22bb8d7b38b3bd66ffcc7d4ee446adfee943) by [@cortinico](https://github.com/cortinico)) +- **New Architecture:** Deprecate `BridgelessReactContext.getCatalystInstance()` method ([4583fbe052](https://github.com/facebook/react-native/commit/4583fbe052924df1ad030e51ad80e8d754a4c5a4) by [@cortinico](https://github.com/cortinico)) +- **New Architecture:** Deprecate legacy architecture classes ReactInstanceManager and ReactInstanceManagerBuilder, these classes will be deleted in a future release ([fb84932e48](https://github.com/facebook/react-native/commit/fb84932e4894a45c0a2725e1d665acdf7bcea435) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Depreacate `CoreModulesPackage` and `NativeModuleRegistryBuilder` legacy architecture classes, these classes unused in the new architecture and will be deleted in the future ([d3bbbd893a](https://github.com/facebook/react-native/commit/d3bbbd893acd500237ab4e1778c6a2e0fe1948a9) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate Legacy Architecture ViewManagers, these classes are not used as part of the new architecture and will be deleted in the future ([da74d5da2c](https://github.com/facebook/react-native/commit/da74d5da2cac5306e37368c65490c434e7ff9f4f) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture ShadowNode classes included in React Native ([07091a9ae8](https://github.com/facebook/react-native/commit/07091a9ae8d70a601d969d9def4952563d3b7bcf) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Depreacte all LegacyArchitecture classes from the bridge package ([c1f7c5e321](https://github.com/facebook/react-native/commit/c1f7c5e3217a7e8a77a859652aadff2a41e3ea58) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture class UIManagerProvider ([b29b86f275](https://github.com/facebook/react-native/commit/b29b86f27553eac50daa18ffb6bca07be3f24f25) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate BridgeDevSupportManager and JSInstance ([25c011eb4d](https://github.com/facebook/react-native/commit/25c011eb4d403040b57e338bec704769de20793c) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate NativeModuleRegistry Legacy Architecture class ([22e4c25211](https://github.com/facebook/react-native/commit/22e4c252116da1a6658b15a84720e0ee314dddd6) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate subset of LegacyArchitecture classes in com/facebook/react/bridge ([78a3ff81eb](https://github.com/facebook/react-native/commit/78a3ff81eb38ae26fb15106580de841477897101) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture class FrescoBasedReactTextInlineImageShadowNode ([25f466cc4d](https://github.com/facebook/react-native/commit/25f466cc4dd28c962b967475c04c284d26efb722) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate Legacy Architecture class CallbackImpl ([718126fcf0](https://github.com/facebook/react-native/commit/718126fcf0296969ee659c31fae51b4317c896d3) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture class JavaMethodWrapper ([19a99dd088](https://github.com/facebook/react-native/commit/19a99dd0882d786daea3db486fd3aed3c10419b5) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate Legacy Architecture ShadowNode classes ([c4715886a9](https://github.com/facebook/react-native/commit/c4715886a917eb3eb63aab366de5225090dff5a1) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture UIManagerModules class ([85610c8b43](https://github.com/facebook/react-native/commit/85610c8b43ea132154cddcfed973ee6ceb3e55b3) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture classes from com/facebook/react/uimanager ([7f5b2b8f84](https://github.com/facebook/react-native/commit/7f5b2b8f84d7941891a447978c6adc17929ef87f) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture classes from package com.facebook.react.uimanager ([39d24bade3](https://github.com/facebook/react-native/commit/39d24bade317920544a3715e3a1f131663d8cded) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** Deprecate LegacyArchitecture classes from LayoutAnimation package ([f67078df07](https://github.com/facebook/react-native/commit/f67078df07b6c9ad995eb43ff47fc4a43bb2eaee) by [@mdvacca](https://github.com/mdvacca)) +- **New Architecture:** ReactPackageLogger is not supported in the new architecture and being deprecated ([65671108f6](https://github.com/facebook/react-native/commit/65671108f69d9b23a011841102e4141293581d9c) by [@mdvacca](https://github.com/mdvacca)) + +#### iOS specific + +- **DevMenu:** Remove bridge mode title and description from React Native Dev Menu title ([775daf5972](https://github.com/facebook/react-native/commit/775daf597280db94354ed484f2ce81690f1eb7b0) by [@sbuggay](https://github.com/sbuggay)) +- **New Architecture:** Deprecate all the objc classes not used by interop, or the new architecture. ([70f53ac4ea](https://github.com/facebook/react-native/commit/70f53ac4ea144020560906f5931e480ed4dee87c) by [@RSNara](https://github.com/RSNara)) + +### Removed + +- **New Architecture:** Core: Remove legacy components ([9c8a4c2297](https://github.com/facebook/react-native/commit/9c8a4c22973c7ce6fcf6b5d22c6d5fd4c6dc0d92) by [@RSNara](https://github.com/RSNara)) + +#### Android specific + +- **DefaultReactHost:** Delete unused `DefaultReactHost.getDefaultReactHost()` overload ([d35ddb5e59](https://github.com/facebook/react-native/commit/d35ddb5e59a8cb990dd61a154a8e15e9542f8b15) by [@cortinico](https://github.com/cortinico)) +- **DefaultReactHost:** Remove deprecated DefaultReactHost.getDefaultReactHost() overload - part 2 ([bda6acf3b0](https://github.com/facebook/react-native/commit/bda6acf3b08779c0dae7bdadbc9913eea79acd0d) by [@cortinico](https://github.com/cortinico)) +- **DefaultReactHost:** Remove deprecated DefaultReactHost.getDefaultReactHost() overload - part 1 ([474f455a75](https://github.com/facebook/react-native/commit/474f455a7591049382da0d0308ddd21589f0cc7e) by [@cortinico](https://github.com/cortinico)) +- **Inspector:** Removed unused `Inspector` public class from React Android ([cf528526cc](https://github.com/facebook/react-native/commit/cf528526cc375f1003125cf63f66fbd88790ceae) by [@cortinico](https://github.com/cortinico)) +- **JSONArguments:** Remove the `com.facebook.react.bridge.JSONArguments` class ([04ae15d99b](https://github.com/facebook/react-native/commit/04ae15d99bb2ee6f7987bbe8c3d7acfdd46a482f) by [@cortinico](https://github.com/cortinico)) +- **MessageQueueThreadPerfStats:** Deprecated MessageQueueThreadPerfStats API and replaced with stub. ([3bf5cb3d0e](https://github.com/facebook/react-native/commit/3bf5cb3d0e7d9d1749ef19a8392b9bbd3ec7ab7d) by [@javache](https://github.com/javache)) + +### Fixed + +- **Accessibility:** Fix for setting the default value for accessibility props ([586f5ba89c](https://github.com/facebook/react-native/commit/586f5ba89cc20a81a9e2d5d0f2708e9cd1b440c0) by Vineeth K) +- **Accessibility:** `aria-hidden` support for `Text`, non-editable `TextInput` and `Image` ([0f39fc3000](https://github.com/facebook/react-native/commit/0f39fc3000411a43711814e0ab9cca1f7093b625) by [@mdjastrzebski](https://github.com/mdjastrzebski)) +- **Build:** Fixed babel plugin validation error when coverage instrumentation is enabled ([191ddc1ec7](https://github.com/facebook/react-native/commit/191ddc1ec72be6641ebb8b9cb729cf0e142fff55) by Umar Mohammad) +- **Casting:** Casting rawValue to int was incorrectly truncating ([31b9f10364](https://github.com/facebook/react-native/commit/31b9f103645e67586bdfc5c2f590c28c04ca3871) by [@javache](https://github.com/javache)) +- **Codegen:** Help Codegen find library's package.json if some libraries using `exports` field in their package.json file and the `./package.json` subpath is not explicitly defined ([739dfd2141](https://github.com/facebook/react-native/commit/739dfd2141015a8126448bda64a559f5bf22672e) by [@RakaDoank](https://github.com/RakaDoank)) +- **Hermes:** Change leftover references to `hermes.framework` to `hermesvm.framework` ([7f051c5470](https://github.com/facebook/react-native/commit/7f051c54701b3585f76f63846abbf7e68e2688d2) by [@j-piasecki](https://github.com/j-piasecki)) +- **Performance Panel:** Fix typo in Performance.js type checking condition ([6caf2dfa38](https://github.com/facebook/react-native/commit/6caf2dfa382fd4f1184b8d21b030c36687a256e4) by [@YangJonghun](https://github.com/YangJonghun)) +- **Performance Panel:** Add default cases to switch statements in headers ([323fe3a5d4](https://github.com/facebook/react-native/commit/323fe3a5d471ae5a2f94d5c2bd13cc97feffe0a5) by [@NSProgrammer](https://github.com/NSProgrammer)) +- **ReactCommon:** Bring back ContextContainer::Shared = std::shared_ptr alias ([daeb6e99ab](https://github.com/facebook/react-native/commit/daeb6e99abbca2b6395a9a703d2b0bb9e5091fb7) by [@christophpurrer](https://github.com/christophpurrer)) +- **ReactCommon:** Bring back SharedImageManager = std::shared_ptr alias ([4718b35259](https://github.com/facebook/react-native/commit/4718b35259135b3503033a0061ae84e15d4eb450) by [@christophpurrer](https://github.com/christophpurrer)) +- **ReactCommon:** Fixed Type Conversion Error in DynamicEventPayload ([ff38d59cff](https://github.com/facebook/react-native/commit/ff38d59cff92e0a50f0dd70384fbc4dd11d969c4) by Harini Malothu) +- **ReactCommon:** Fixed Type Conversion Error in CSSHexColor ([2ca88a0069](https://github.com/facebook/react-native/commit/2ca88a0069969bf115da6f0ea9f2fbbae9c9226c) by [@anupriya13](https://github.com/anupriya13)) +- **TestCallInvoker:** Fix memory leak in TestCallInvoker ([9f2fbc23e4](https://github.com/facebook/react-native/commit/9f2fbc23e48af9be56b3729d514fbb3fff4ba376) by [@christophpurrer](https://github.com/christophpurrer)) + +#### Android specific + +- **Accessability:** Stabilize custom accessibility action IDs to prevent "incompatible action" errors in TalkBack. ([626568f9a3](https://github.com/facebook/react-native/commit/626568f9a3f956a52f6c55df1dc3bc5cd017e353) by [@leg234-png](https://github.com/leg234-png)) +- **Determinism:** Turned off build IDs for native libraries, fixing issues with reproducibility ([4b8dbe7642](https://github.com/facebook/react-native/commit/4b8dbe7642be53d0ccfc68ca8c9b3f5e750a68c0) by [@Rexogamer](https://github.com/Rexogamer)) +- **DevTools:** Fix stack trace linkifying failing when using Android emulator and other situations where the device and debugger have different bundle urls ([794df48ad6](https://github.com/facebook/react-native/commit/794df48ad6a259022e66de1a38ff54b5ec67c3e4) by [@vzaidman](https://github.com/vzaidman)) +- **Edge to Edge:** Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled ([3b185e4bce](https://github.com/facebook/react-native/commit/3b185e4bcef24e0689cccd4cf250d469b114d4da) by [@zoontek](https://github.com/zoontek)) +- **Fonts:** Update font scale when recreating `RootView` ([5cda3065ce](https://github.com/facebook/react-native/commit/5cda3065ce635460a7458cbab5c10e24bea3bfe2) by [@j-piasecki](https://github.com/j-piasecki)) +- **Fonts:** Fix incorrect positioning of inline view at the end of string when RTL text in LTR container ([7f224941bb](https://github.com/facebook/react-native/commit/7f224941bb807919b487d8e1634dd2124f9258b8) by [@NickGerleman](https://github.com/NickGerleman)) +- **Locale:** Use the first available locale instead of the default one to decide `isDevicePreferredLanguageRTL` ([a03780d279](https://github.com/facebook/react-native/commit/a03780d279d0944e0dcbbf5a93680775006598b0) by Kaining Zhong) +- **New Architecture:** Correctly account for insets on first render of Modals on New Arch ([2e76fc8e8e](https://github.com/facebook/react-native/commit/2e76fc8e8ea01fbce5bd131f675364e688f49088) by [@cortinico](https://github.com/cortinico)) +- **Performance:** Fix mounting is very slow on Android by shipping native transform optimizations ([c557311ed8](https://github.com/facebook/react-native/commit/c557311ed836cded8548c5bca32f3eded0abc7ff) by [@cortinico](https://github.com/cortinico)) +- **Scroll:** Fixed an issue where shadow tree and native tree layouts mismatch at the end of a scroll event ([1828c53f85](https://github.com/facebook/react-native/commit/1828c53f85faf599a485b3859f8b62586696265f) by [@Abbondanzo](https://github.com/Abbondanzo)) +- **Start up:** Fix wrong default for `jsBundleAssetPath` on `DefaultReactHost` ([2246e2b82c](https://github.com/facebook/react-native/commit/2246e2b82cf0c433f9a9b385ea98e532c6f322c6) by [@cortinico](https://github.com/cortinico)) + +#### iOS specific + +- **Build:** Fixed using USE_FRAMEWORKS (static/dynamic) with precompiled binaries ([e723ca4d6b](https://github.com/facebook/react-native/commit/e723ca4d6b86d5a98449498395c700513ceba555) by [@chrfalch](https://github.com/chrfalch)) +- **Build:** Non-UTF8 crashes Info.plist local frameworks ([91e69b5d4c](https://github.com/facebook/react-native/commit/91e69b5d4c768278680a8d9ae979bc267624ce98) by [@philipheinser](https://github.com/philipheinser)) +- **Build:** Fixed variable naming error in `set_fast_float_config` method in `react_native_pods.rb` ([327057fad5](https://github.com/facebook/react-native/commit/327057fad5c78a95e6c039bfe380d78672e83a43) by [@eliotfallon213](https://github.com/eliotfallon213)) +- **Build:** Fix pure cocoapods dynamic framework build ([aa4555eaf1](https://github.com/facebook/react-native/commit/aa4555eaf1b6aab83660c600e867fa6c2da4128e) by [@cipolleschi](https://github.com/cipolleschi)) +- **Native Modules:** Fix concurrent calls into resolve/reject inside native modules ([dc879950d1](https://github.com/facebook/react-native/commit/dc879950d196dfd429229f1c4c8e743ef1799d11) by [@RSNara](https://github.com/RSNara)) +- **New Architecture:** Fix overriding (xc)framework Info.plist files with RCTNewArchEnabled field ([f84514a88b](https://github.com/facebook/react-native/commit/f84514a88be00f8dcae7972f84aa89d829392a58) by [@msynowski](https://github.com/msynowski)) +- **RCTPullToRefreshViewComponentView:** Properly initialize the `RCTPullToRefreshViewComponentView` ([27217e8bd6](https://github.com/facebook/react-native/commit/27217e8bd601757b5db6efc022db428b552a2aa4) by [@cipolleschi](https://github.com/cipolleschi)) +- **RCTReactNativeFactory:** Ask the delegate for `getModuleForClass` and `getModuleInstanceFromClass` ([85b47afb48](https://github.com/facebook/react-native/commit/85b47afb48e50b036d2c2c79a008f571d3bfcb43) by [@cipolleschi](https://github.com/cipolleschi)) +- **ScrollView:** Correctly propagate `ScrollView` props to `RefreshControl` ([09daad27ea](https://github.com/facebook/react-native/commit/09daad27ea22b83fab65176ea3c7f5f1488ba408) by [@cipolleschi](https://github.com/cipolleschi)) +- **ScrollView:** Make sure that `ScrollView` recycled refresh control have the right props setup. ([21b93d8d7d](https://github.com/facebook/react-native/commit/21b93d8d7d46a26f728df19764f85a8aebf318bb) by [@cipolleschi](https://github.com/cipolleschi)) +- **Switch:** Fixed a crash when rendering the `Switch` component ([28275a0f7b](https://github.com/facebook/react-native/commit/28275a0f7b182a215010d47fb841d9c2c36bb24c) by [@cipolleschi](https://github.com/cipolleschi)) +- **Text:** Fix selectable prop not working correctly ([f004cd39bc](https://github.com/facebook/react-native/commit/f004cd39bc4b632006085cbcf61df52bc5d25242) by [@iamAbhi-916](https://github.com/iamAbhi-916)) +- **TextInput:** Update TextInput recycling logic to clean up the `inputAccessoryView` dependency. ([eb08f54594](https://github.com/facebook/react-native/commit/eb08f545948de9e2eca91ab3cb7569670c553b15) by [@ArturKalach](https://github.com/ArturKalach)) +- **TextInput:** Fixed TextInput behavior when `maxLength={null}` is passed ([56ad53cb14](https://github.com/facebook/react-native/commit/56ad53cb14b5c842714fcf976b6ba81f68c140f2) by [@cipolleschi](https://github.com/cipolleschi)) +- **View:** Inline `View` alignment with `lineHeight` in Text ([6da351a5ed](https://github.com/facebook/react-native/commit/6da351a5ed80a10138a5558afcb380410c8a93c9) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) + +### Security + +- **Network:** Fixed vulnerability on undici and on-headers ([dd00c9055a](https://github.com/facebook/react-native/commit/dd00c9055a8f0c9ceac1716385a8a9874f7a4c2e) by [@cipolleschi](https://github.com/cipolleschi)) + ## v0.81.1 ### Added From 716cbae68d143ea28c383d9346a077df483f041b Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Tue, 2 Sep 2025 07:06:09 -0700 Subject: [PATCH 0013/1110] support ObjectAnimatedNode (#53517) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53517 ## Changelog: [Internal] [Added] - support ObjectAnimatedNode Reviewed By: christophpurrer, fabriziocucci Differential Revision: D81260836 fbshipit-source-id: 82bdda59d54140189684003adfb1adf5c8e2904d --- .../animated/NativeAnimatedNodesManager.cpp | 3 + .../renderer/animated/nodes/AnimatedNode.cpp | 2 + .../renderer/animated/nodes/AnimatedNode.h | 1 + .../animated/nodes/ObjectAnimatedNode.cpp | 121 ++++++++++++++++++ .../animated/nodes/ObjectAnimatedNode.h | 38 ++++++ .../animated/nodes/PropsAnimatedNode.cpp | 7 + .../animated/nodes/StyleAnimatedNode.cpp | 7 + .../animated/tests/AnimatedNodeTests.cpp | 51 ++++++++ 8 files changed, 230 insertions(+) create mode 100644 packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.cpp create mode 100644 packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.h diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index 18e3dd46cc4d..b2fecda32d6f 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -145,6 +146,8 @@ std::unique_ptr NativeAnimatedNodesManager::animatedNode( return std::make_unique(tag, config, *this); case AnimatedNodeType::Round: return std::make_unique(tag, config, *this); + case AnimatedNodeType::Object: + return std::make_unique(tag, config, *this); default: LOG(WARNING) << "Cannot create AnimatedNode of type " << typeName << ", it's not implemented yet"; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.cpp b/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.cpp index b1fb2221a7d3..4d00570ce81a 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.cpp @@ -73,6 +73,8 @@ std::optional AnimatedNode::getNodeTypeByName( return AnimatedNodeType::Tracking; } else if (nodeTypeName == "round") { return AnimatedNodeType::Round; + } else if (nodeTypeName == "object") { + return AnimatedNodeType::Object; } else { return std::nullopt; } diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.h b/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.h index 4c1f37fd9780..d26b9fae526f 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/AnimatedNode.h @@ -32,6 +32,7 @@ enum class AnimatedNodeType { Tracking, Color, Round, + Object }; class NativeAnimatedNodesManager; diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.cpp b/packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.cpp new file mode 100644 index 000000000000..61eedebeb493 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* + * Adapted from react-native-windows under the MIT license. + */ + +#include "ObjectAnimatedNode.h" + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +ObjectAnimatedNode::ObjectAnimatedNode( + Tag tag, + const folly::dynamic& config, + NativeAnimatedNodesManager& manager) + : AnimatedNode(tag, config, manager, AnimatedNodeType::Object) {} + +void ObjectAnimatedNode::collectViewUpdates( + std::string propKey, + folly::dynamic& props) { + const auto& value = getConfig()["value"]; + switch (value.type()) { + case folly::dynamic::OBJECT: { + props.insert(propKey, collectViewUpdatesObjectHelper(value)); + } break; + case folly::dynamic::ARRAY: { + props.insert(propKey, collectViewUpdatesArrayHelper(value)); + } break; + default: { + LOG(ERROR) << "Invalid value type for ObjectAnimatedNode"; + } break; + } +} + +folly::dynamic ObjectAnimatedNode::collectViewUpdatesObjectHelper( + const folly::dynamic& value) const { + folly::dynamic result = folly::dynamic::object(); + for (const auto& valueProp : value.items()) { + result.insert(valueProp.first.asString(), getValueProp(valueProp.second)); + } + return result; +} + +folly::dynamic ObjectAnimatedNode::collectViewUpdatesArrayHelper( + const folly::dynamic& value) const { + folly::dynamic result = folly::dynamic::array(); + for (const auto& valueProp : value) { + result.push_back(getValueProp(valueProp)); + } + return result; +} + +folly::dynamic ObjectAnimatedNode::getValueProp( + const folly::dynamic& prop) const { + switch (prop.type()) { + case folly::dynamic::OBJECT: { + if (auto itNodeTag = prop.find("nodeTag"); + itNodeTag != prop.items().end()) { + auto nodeTag = static_cast(itNodeTag->second.asInt()); + if (auto node = manager_->getAnimatedNode(nodeTag)) { + switch (node->type()) { + case AnimatedNodeType::Value: + case AnimatedNodeType::Interpolation: + case AnimatedNodeType::Modulus: + case AnimatedNodeType::Round: + case AnimatedNodeType::Diffclamp: + // Operators + case AnimatedNodeType::Addition: + case AnimatedNodeType::Subtraction: + case AnimatedNodeType::Multiplication: + case AnimatedNodeType::Division: { + if (const auto valueNode = + manager_->getAnimatedNode(nodeTag)) { + if (valueNode->getIsColorValue()) { + return static_cast(valueNode->getValue()); + } else { + return valueNode->getValue(); + } + } + } break; + case AnimatedNodeType::Color: { + if (const auto colorAnimNode = + manager_->getAnimatedNode(nodeTag)) { + return static_cast(colorAnimNode->getColor()); + } + } break; + default: + break; + } + } + } else { + return collectViewUpdatesObjectHelper(prop); + } + } break; + case folly::dynamic::ARRAY: { + return collectViewUpdatesArrayHelper(prop); + }; + case folly::dynamic::NULLT: + case folly::dynamic::BOOL: + case folly::dynamic::DOUBLE: + case folly::dynamic::INT64: + case folly::dynamic::STRING: { + return prop; + }; + } + LOG(ERROR) << "Invalid prop type for ObjectAnimatedNode"; + return nullptr; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.h b/packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.h new file mode 100644 index 000000000000..4be4c7596d9b --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/ObjectAnimatedNode.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* + * Adapted from react-native-windows under the MIT license. + */ + +#pragma once + +#include "AnimatedNode.h" + +#include + +namespace facebook::react { + +class ObjectAnimatedNode final : public AnimatedNode { + public: + ObjectAnimatedNode( + Tag tag, + const folly::dynamic& config, + NativeAnimatedNodesManager& manager); + void collectViewUpdates(std::string propKey, folly::dynamic& props); + + private: + folly::dynamic collectViewUpdatesObjectHelper( + const folly::dynamic& value) const; + + folly::dynamic collectViewUpdatesArrayHelper( + const folly::dynamic& value) const; + + folly::dynamic getValueProp(const folly::dynamic& prop) const; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp index cc7cb1d878a2..8303b74741b8 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -126,6 +127,12 @@ void PropsAnimatedNode::update(bool forceFabricCommit) { styleNode->collectViewUpdates(props_); } } break; + case AnimatedNodeType::Object: { + if (const auto objectNode = + manager_->getAnimatedNode(nodeTag)) { + objectNode->collectViewUpdates(propName, props_); + } + } break; case AnimatedNodeType::Props: case AnimatedNodeType::Tracking: case AnimatedNodeType::Transform: diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/StyleAnimatedNode.cpp b/packages/react-native/ReactCommon/react/renderer/animated/nodes/StyleAnimatedNode.cpp index cb758bf8ca80..430043aa13ba 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/StyleAnimatedNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/StyleAnimatedNode.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -82,6 +83,12 @@ void StyleAnimatedNode::collectViewUpdates(folly::dynamic& props) { static_cast(colorAnimNode->getColor())); } } break; + case AnimatedNodeType::Object: { + if (const auto objectNode = + manager_->getAnimatedNode(nodeTag)) { + objectNode->collectViewUpdates(propName, props); + } + } break; case AnimatedNodeType::Tracking: case AnimatedNodeType::Style: case AnimatedNodeType::Props: diff --git a/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp b/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp index 9be0a5d95130..7befdef6e07e 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp @@ -8,6 +8,7 @@ #include "AnimationTestsBase.h" #include +#include #include #include @@ -214,4 +215,54 @@ TEST_F(AnimatedNodeTests, DiffClampAnimatedNode) { EXPECT_EQ(nodesManager_->getValue(diffClampTag), 1); } +TEST_F(AnimatedNodeTests, ObjectAnimatedNode) { + initNodesManager(); + + auto rootTag = getNextRootViewTag(); + + auto valueTag = ++rootTag; + auto objectTag = ++rootTag; + nodesManager_->createAnimatedNode( + valueTag, + folly::dynamic::object("type", "value")("value", 4)("offset", 0)); + + nodesManager_->createAnimatedNode( + objectTag, + folly::dynamic::object("type", "object")( + "value", + folly::dynamic::array( + folly::dynamic::object( + "translate3d", + folly::dynamic::object("x", 1)("y", 0)("z", 0)), + folly::dynamic::object( + "rotate3d", + folly::dynamic::object("x", 1)("y", 0)("z", 0)( + "angle", "180deg")), + folly::dynamic::object( + "scale3d", folly::dynamic::object("nodeTag", valueTag))))); + + nodesManager_->connectAnimatedNodes(valueTag, objectTag); + + const auto objectNode = + nodesManager_->getAnimatedNode(objectTag); + folly::dynamic collectedProps = folly::dynamic::object(); + objectNode->collectViewUpdates("test", collectedProps); + + const auto expected = folly::dynamic::object( + "test", + folly::dynamic::array( + folly::dynamic::object( + "translate3d", folly::dynamic::object("x", 1)("y", 0)("z", 0)), + folly::dynamic::object( + "rotate3d", + folly::dynamic::object("x", 1)("y", 0)("z", 0)( + "angle", "180deg")), + folly::dynamic::object("scale3d", 4))); + EXPECT_EQ(collectedProps["test"].size(), 3); + EXPECT_EQ(collectedProps["test"][0]["translate3d"]["x"], 1); + EXPECT_EQ(collectedProps["test"][1]["rotate3d"]["y"], 0); + EXPECT_EQ(collectedProps["test"][1]["rotate3d"]["angle"], "180deg"); + EXPECT_EQ(collectedProps["test"][2]["scale3d"], 4); +} + } // namespace facebook::react From b2b992c5bbfd498ecf50c7128784280b22215d85 Mon Sep 17 00:00:00 2001 From: generatedunixname537391475639613 Date: Tue, 2 Sep 2025 08:11:47 -0700 Subject: [PATCH 0014/1110] xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt (#53545) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53545 Reviewed By: cortinico Differential Revision: D81428987 fbshipit-source-id: 7bd04528384bd96f659cf969806d367296665d97 --- .../com/facebook/react/modules/image/ImageLoaderModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt index adcf2192b505..d76da5ffc600 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt @@ -230,8 +230,8 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL override fun doInBackgroundGuarded(vararg params: Void) { val result = buildReadableMap { val imagePipeline: ImagePipeline = this@ImageLoaderModule.imagePipeline - repeat(uris.size()) { - val uriString = uris.getString(it) + repeat(uris.size()) { index -> + val uriString = uris.getString(index) if (!uriString.isNullOrEmpty()) { val uri = Uri.parse(uriString) if (imagePipeline.isInBitmapMemoryCache(uri)) { From e1071ce683768ef8162a5fd3abb6e876ed6b524b Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Tue, 2 Sep 2025 10:19:57 -0700 Subject: [PATCH 0015/1110] Clean up legacy CSSBackgroundDrawable.java and enablNewBackgroundAndBorderDrawables featureflag (#53534) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53534 BackgroundDrawable and BorderDrawable have already substituted CSSBackgroundDrawable en every Android surface. - Deleting CSSBackgroundDrawable.java and its callsites - Deleting enableNewBackgroundAndDrawable featureflag Just cleaning up what at this point is just dead code. Changelog: [Internal] Reviewed By: javache Differential Revision: D81330969 fbshipit-source-id: bcf66ec8d3225802432ae1d93a2b26ea65cfcda0 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../uimanager/BackgroundStyleApplicator.kt | 198 +-- .../drawable/CSSBackgroundDrawable.java | 1474 ----------------- .../drawable/CompositeBackgroundDrawable.kt | 32 - .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 92 +- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 11 - .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 23 files changed, 109 insertions(+), 1824 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index feea7276bafd..79573182f0ca 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<34c12f5a31aab5bfb874953f1beefef1>> + * @generated SignedSource<<6a0977519362184efd85bd1807b2ad25>> */ /** @@ -222,12 +222,6 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableNetworkEventReporting(): Boolean = accessor.enableNetworkEventReporting() - /** - * Use BackgroundDrawable and BorderDrawable instead of CSSBackgroundDrawable - */ - @JvmStatic - public fun enableNewBackgroundAndBorderDrawables(): Boolean = accessor.enableNewBackgroundAndBorderDrawables() - /** * Enables caching text layout artifacts for later reuse */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 9fadb1e74165..9e18b80a559e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<392da016e0bf4193b72c44a508811e10>> + * @generated SignedSource<> */ /** @@ -52,7 +52,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null private var enableNativeCSSParsingCache: Boolean? = null private var enableNetworkEventReportingCache: Boolean? = null - private var enableNewBackgroundAndBorderDrawablesCache: Boolean? = null private var enablePreparedTextLayoutCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null private var enableResourceTimingAPICache: Boolean? = null @@ -378,15 +377,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } - override fun enableNewBackgroundAndBorderDrawables(): Boolean { - var cached = enableNewBackgroundAndBorderDrawablesCache - if (cached == null) { - cached = ReactNativeFeatureFlagsCxxInterop.enableNewBackgroundAndBorderDrawables() - enableNewBackgroundAndBorderDrawablesCache = cached - } - return cached - } - override fun enablePreparedTextLayout(): Boolean { var cached = enablePreparedTextLayoutCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 03e35b7436f2..5bff149788ed 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<7b0b5efc545427de6f104cc36f42d0a9>> */ /** @@ -92,8 +92,6 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableNetworkEventReporting(): Boolean - @DoNotStrip @JvmStatic public external fun enableNewBackgroundAndBorderDrawables(): Boolean - @DoNotStrip @JvmStatic public external fun enablePreparedTextLayout(): Boolean @DoNotStrip @JvmStatic public external fun enablePropsUpdateReconciliationAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index fbb9c291afbb..3b82d5df4de5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<719706a983a073b6c286c49d993f7f80>> + * @generated SignedSource<<7de4be16480ca4df9f2a6655a469c6c7>> */ /** @@ -87,8 +87,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableNetworkEventReporting(): Boolean = false - override fun enableNewBackgroundAndBorderDrawables(): Boolean = true - override fun enablePreparedTextLayout(): Boolean = false override fun enablePropsUpdateReconciliationAndroid(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index d591e22bf5b6..62648627588a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<594815ba6a984c460ab8bddd91c5cae2>> + * @generated SignedSource<<72146cdfb6cc16ead73858c970e4221a>> */ /** @@ -56,7 +56,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null private var enableNativeCSSParsingCache: Boolean? = null private var enableNetworkEventReportingCache: Boolean? = null - private var enableNewBackgroundAndBorderDrawablesCache: Boolean? = null private var enablePreparedTextLayoutCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null private var enableResourceTimingAPICache: Boolean? = null @@ -414,16 +413,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } - override fun enableNewBackgroundAndBorderDrawables(): Boolean { - var cached = enableNewBackgroundAndBorderDrawablesCache - if (cached == null) { - cached = currentProvider.enableNewBackgroundAndBorderDrawables() - accessedFeatureFlags.add("enableNewBackgroundAndBorderDrawables") - enableNewBackgroundAndBorderDrawablesCache = cached - } - return cached - } - override fun enablePreparedTextLayout(): Boolean { var cached = enablePreparedTextLayoutCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 91114a5074c1..bf081d50419f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<47efc0544de6bdeec45ee4a7f1aa31fa>> */ /** @@ -87,8 +87,6 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableNetworkEventReporting(): Boolean - @DoNotStrip public fun enableNewBackgroundAndBorderDrawables(): Boolean - @DoNotStrip public fun enablePreparedTextLayout(): Boolean @DoNotStrip public fun enablePropsUpdateReconciliationAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt index cb0d78ed04dd..b7cc724027db 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BackgroundStyleApplicator.kt @@ -19,14 +19,12 @@ import android.widget.ImageView import androidx.annotation.ColorInt import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.annotations.UnstableReactNativeAPI -import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.uimanager.PixelUtil.dpToPx import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.react.uimanager.common.UIManagerType import com.facebook.react.uimanager.common.ViewUtil import com.facebook.react.uimanager.drawable.BackgroundDrawable import com.facebook.react.uimanager.drawable.BorderDrawable -import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable import com.facebook.react.uimanager.drawable.CompositeBackgroundDrawable import com.facebook.react.uimanager.drawable.InsetBoxShadowDrawable import com.facebook.react.uimanager.drawable.MIN_INSET_BOX_SHADOW_SDK_VERSION @@ -59,11 +57,7 @@ public object BackgroundStyleApplicator { return } - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - ensureBackgroundDrawable(view).backgroundColor = color ?: Color.TRANSPARENT - } else { - ensureCSSBackground(view).color = color ?: Color.TRANSPARENT - } + ensureBackgroundDrawable(view).backgroundColor = color ?: Color.TRANSPARENT } @JvmStatic @@ -71,21 +65,13 @@ public object BackgroundStyleApplicator { view: View, backgroundImageLayers: List?, ): Unit { - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - ensureBackgroundDrawable(view).backgroundImageLayers = backgroundImageLayers - } else { - ensureCSSBackground(view).setBackgroundImage(backgroundImageLayers) - } + ensureBackgroundDrawable(view).backgroundImageLayers = backgroundImageLayers } @JvmStatic @ColorInt public fun getBackgroundColor(view: View): Int? { - return if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - getBackground(view)?.backgroundColor - } else { - getCSSBackground(view)?.color - } + return getBackground(view)?.backgroundColor } @JvmStatic @@ -94,16 +80,12 @@ public object BackgroundStyleApplicator { composite.borderInsets = composite.borderInsets ?: BorderInsets() composite.borderInsets?.setBorderWidth(edge, width) - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - ensureBorderDrawable(view).setBorderWidth(edge.toSpacingType(), width?.dpToPx() ?: Float.NaN) - composite.background?.borderInsets = composite.borderInsets - composite.border?.borderInsets = composite.borderInsets + ensureBorderDrawable(view).setBorderWidth(edge.toSpacingType(), width?.dpToPx() ?: Float.NaN) + composite.background?.borderInsets = composite.borderInsets + composite.border?.borderInsets = composite.borderInsets - composite.background?.invalidateSelf() - composite.border?.invalidateSelf() - } else { - ensureCSSBackground(view).setBorderWidth(edge.toSpacingType(), width?.dpToPx() ?: Float.NaN) - } + composite.background?.invalidateSelf() + composite.border?.invalidateSelf() composite.borderInsets = composite.borderInsets ?: BorderInsets() composite.borderInsets?.setBorderWidth(edge, width) @@ -117,32 +99,23 @@ public object BackgroundStyleApplicator { @JvmStatic public fun getBorderWidth(view: View, edge: LogicalEdge): Float? { - return if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - val width = getBorder(view)?.borderWidth?.getRaw(edge.toSpacingType()) - if (width == null || width.isNaN()) null else width.pxToDp() + val width = getBorder(view)?.borderWidth?.getRaw(edge.toSpacingType()) + if (width == null || width.isNaN()) { + return null } else { - val width = getCSSBackground(view)?.getBorderWidth(edge.toSpacingType()) - if (width == null || width.isNaN()) null else width.pxToDp() + return width.pxToDp() } } @JvmStatic public fun setBorderColor(view: View, edge: LogicalEdge, @ColorInt color: Int?): Unit { - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - ensureBorderDrawable(view).setBorderColor(edge, color) - } else { - ensureCSSBackground(view).setBorderColor(edge.toSpacingType(), color) - } + ensureBorderDrawable(view).setBorderColor(edge, color) } @JvmStatic @ColorInt public fun getBorderColor(view: View, edge: LogicalEdge): Int? { - return if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - getBorder(view)?.getBorderColor(edge) - } else { - getCSSBackground(view)?.getBorderColor(edge.toSpacingType()) - } + return getBorder(view)?.getBorderColor(edge) } @JvmStatic @@ -156,19 +129,14 @@ public object BackgroundStyleApplicator { compositeBackgroundDrawable.borderRadius ?: BorderRadiusStyle() compositeBackgroundDrawable.borderRadius?.set(corner, radius) - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - if (view is ImageView) { - ensureBackgroundDrawable(view) - } - compositeBackgroundDrawable.background?.borderRadius = - compositeBackgroundDrawable.borderRadius - compositeBackgroundDrawable.border?.borderRadius = compositeBackgroundDrawable.borderRadius - - compositeBackgroundDrawable.background?.invalidateSelf() - compositeBackgroundDrawable.border?.invalidateSelf() - } else { - ensureCSSBackground(view).setBorderRadius(corner, radius) + if (view is ImageView) { + ensureBackgroundDrawable(view) } + compositeBackgroundDrawable.background?.borderRadius = compositeBackgroundDrawable.borderRadius + compositeBackgroundDrawable.border?.borderRadius = compositeBackgroundDrawable.borderRadius + + compositeBackgroundDrawable.background?.invalidateSelf() + compositeBackgroundDrawable.border?.invalidateSelf() if (Build.VERSION.SDK_INT >= MIN_OUTSET_BOX_SHADOW_SDK_VERSION) { for (shadow in @@ -191,29 +159,17 @@ public object BackgroundStyleApplicator { @JvmStatic public fun getBorderRadius(view: View, corner: BorderRadiusProp): LengthPercentage? { - return if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - getCompositeBackgroundDrawable(view)?.borderRadius?.get(corner) - } else { - getCSSBackground(view)?.borderRadius?.get(corner) - } + return getCompositeBackgroundDrawable(view)?.borderRadius?.get(corner) } @JvmStatic public fun setBorderStyle(view: View, borderStyle: BorderStyle?) { - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - ensureBorderDrawable(view).borderStyle = borderStyle - } else { - ensureCSSBackground(view).borderStyle = borderStyle - } + ensureBorderDrawable(view).borderStyle = borderStyle } @JvmStatic public fun getBorderStyle(view: View): BorderStyle? { - return if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - getBorder(view)?.borderStyle - } else { - getCSSBackground(view)?.borderStyle - } + return getBorder(view)?.borderStyle } @JvmStatic @@ -342,71 +298,44 @@ public object BackgroundStyleApplicator { @JvmStatic public fun setFeedbackUnderlay(view: View, drawable: Drawable?) { - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - - ensureCompositeBackgroundDrawable(view).withNewFeedbackUnderlay(drawable) - } else { - view.background = ensureCompositeBackgroundDrawable(view).withNewFeedbackUnderlay(drawable) - } + ensureCompositeBackgroundDrawable(view).withNewFeedbackUnderlay(drawable) } @JvmStatic public fun clipToPaddingBox(view: View, canvas: Canvas) { - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - val drawingRect = Rect() - view.getDrawingRect(drawingRect) - - val composite = getCompositeBackgroundDrawable(view) - if (composite == null) { - canvas.clipRect(drawingRect) - return - } - - val paddingBoxRect = RectF() + val drawingRect = Rect() + view.getDrawingRect(drawingRect) - val computedBorderInsets = - composite.borderInsets?.resolve(composite.layoutDirection, view.context) + val composite = getCompositeBackgroundDrawable(view) + if (composite == null) { + canvas.clipRect(drawingRect) + return + } - paddingBoxRect.left = composite.bounds.left + (computedBorderInsets?.left?.dpToPx() ?: 0f) - paddingBoxRect.top = composite.bounds.top + (computedBorderInsets?.top?.dpToPx() ?: 0f) - paddingBoxRect.right = composite.bounds.right - (computedBorderInsets?.right?.dpToPx() ?: 0f) - paddingBoxRect.bottom = - composite.bounds.bottom - (computedBorderInsets?.bottom?.dpToPx() ?: 0f) + val paddingBoxRect = RectF() - if (composite.borderRadius?.hasRoundedBorders() == true) { - val paddingBoxPath = - createPaddingBoxPath( - view, - composite, - paddingBoxRect, - computedBorderInsets, - ) + val computedBorderInsets = + composite.borderInsets?.resolve(composite.layoutDirection, view.context) - paddingBoxPath.offset(drawingRect.left.toFloat(), drawingRect.top.toFloat()) - canvas.clipPath(paddingBoxPath) - } else { - paddingBoxRect.offset(drawingRect.left.toFloat(), drawingRect.top.toFloat()) - canvas.clipRect(paddingBoxRect) - } - } else { - val drawingRect = Rect() - view.getDrawingRect(drawingRect) + paddingBoxRect.left = composite.bounds.left + (computedBorderInsets?.left?.dpToPx() ?: 0f) + paddingBoxRect.top = composite.bounds.top + (computedBorderInsets?.top?.dpToPx() ?: 0f) + paddingBoxRect.right = composite.bounds.right - (computedBorderInsets?.right?.dpToPx() ?: 0f) + paddingBoxRect.bottom = composite.bounds.bottom - (computedBorderInsets?.bottom?.dpToPx() ?: 0f) - val cssBackground = getCSSBackground(view) - if (cssBackground == null) { - canvas.clipRect(drawingRect) - return - } + if (composite.borderRadius?.hasRoundedBorders() == true) { + val paddingBoxPath = + createPaddingBoxPath( + view, + composite, + paddingBoxRect, + computedBorderInsets, + ) - val paddingBoxPath = cssBackground.paddingBoxPath - if (paddingBoxPath != null) { - paddingBoxPath.offset(drawingRect.left.toFloat(), drawingRect.top.toFloat()) - canvas.clipPath(paddingBoxPath) - } else { - val paddingBoxRect = cssBackground.paddingBoxRect - paddingBoxRect.offset(drawingRect.left.toFloat(), drawingRect.top.toFloat()) - canvas.clipRect(paddingBoxRect) - } + paddingBoxPath.offset(drawingRect.left.toFloat(), drawingRect.top.toFloat()) + canvas.clipPath(paddingBoxPath) + } else { + paddingBoxRect.offset(drawingRect.left.toFloat(), drawingRect.top.toFloat()) + canvas.clipRect(paddingBoxRect) } } @@ -431,19 +360,6 @@ public object BackgroundStyleApplicator { private fun getCompositeBackgroundDrawable(view: View): CompositeBackgroundDrawable? = view.background as? CompositeBackgroundDrawable - private fun ensureCSSBackground(view: View): CSSBackgroundDrawable { - val compositeBackgroundDrawable = ensureCompositeBackgroundDrawable(view) - var cssBackground = compositeBackgroundDrawable.cssBackground - - return if (cssBackground != null) { - return cssBackground - } else { - cssBackground = CSSBackgroundDrawable(view.context) - view.background = compositeBackgroundDrawable.withNewCssBackground(cssBackground) - cssBackground - } - } - private fun ensureBackgroundDrawable(view: View): BackgroundDrawable { val compositeBackgroundDrawable = ensureCompositeBackgroundDrawable(view) var background = compositeBackgroundDrawable.background @@ -462,9 +378,6 @@ public object BackgroundStyleApplicator { } } - private fun getCSSBackground(view: View): CSSBackgroundDrawable? = - getCompositeBackgroundDrawable(view)?.cssBackground - private fun getBackground(view: View): BackgroundDrawable? = getCompositeBackgroundDrawable(view)?.background @@ -492,12 +405,7 @@ public object BackgroundStyleApplicator { val compositeBackgroundDrawable = ensureCompositeBackgroundDrawable(view) var outline = compositeBackgroundDrawable.outline if (outline == null) { - val borderRadius = - if (ReactNativeFeatureFlags.enableNewBackgroundAndBorderDrawables()) { - compositeBackgroundDrawable.borderRadius - } else { - ensureCSSBackground(view).borderRadius - } + val borderRadius = compositeBackgroundDrawable.borderRadius outline = OutlineDrawable( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java deleted file mode 100644 index f4983aae190f..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java +++ /dev/null @@ -1,1474 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.uimanager.drawable; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.ComposeShader; -import android.graphics.DashPathEffect; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PathEffect; -import android.graphics.PixelFormat; -import android.graphics.PointF; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Shader; -import android.graphics.drawable.Drawable; -import android.view.View; -import androidx.annotation.Nullable; -import androidx.core.graphics.ColorUtils; -import androidx.core.util.Preconditions; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.common.annotations.UnstableReactNativeAPI; -import com.facebook.react.modules.i18nmanager.I18nUtil; -import com.facebook.react.uimanager.FloatUtil; -import com.facebook.react.uimanager.LengthPercentage; -import com.facebook.react.uimanager.LengthPercentageType; -import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.Spacing; -import com.facebook.react.uimanager.style.BackgroundImageLayer; -import com.facebook.react.uimanager.style.BorderRadiusProp; -import com.facebook.react.uimanager.style.BorderRadiusStyle; -import com.facebook.react.uimanager.style.BorderStyle; -import com.facebook.react.uimanager.style.ComputedBorderRadius; -import com.facebook.react.uimanager.style.CornerRadii; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -/** - * A subclass of {@link Drawable} used for background of {@link - * com.facebook.react.views.view.ReactViewGroup}. It supports drawing background color and borders - * (including rounded borders) by providing a react friendly API (setter for each of those - * properties). - * - *

The implementation tries to allocate as few objects as possible depending on which properties - * are set. E.g. for views with rounded background/borders we allocate {@code - * mInnerClipPathForBorderRadius} and {@code mInnerClipTempRectForBorderRadius}. In case when view - * have a rectangular borders we allocate {@code mBorderWidthResult} and similar. When only - * background color is set we won't allocate any extra/unnecessary objects. - */ -@UnstableReactNativeAPI -@Nullsafe(Nullsafe.Mode.LOCAL) -public class CSSBackgroundDrawable extends Drawable { - - private static final int DEFAULT_BORDER_COLOR = Color.BLACK; - private static final int DEFAULT_BORDER_RGB = 0x00FFFFFF & DEFAULT_BORDER_COLOR; - private static final int DEFAULT_BORDER_ALPHA = (0xFF000000 & DEFAULT_BORDER_COLOR) >>> 24; - // ~0 == 0xFFFFFFFF, all bits set to 1. - private static final int ALL_BITS_SET = ~0; - // 0 == 0x00000000, all bits set to 0. - private static final int ALL_BITS_UNSET = 0; - - private static @Nullable PathEffect getPathEffect(BorderStyle style, float borderWidth) { - switch (style) { - case SOLID: - return null; - - case DASHED: - return new DashPathEffect( - new float[] {borderWidth * 3, borderWidth * 3, borderWidth * 3, borderWidth * 3}, 0); - - case DOTTED: - return new DashPathEffect( - new float[] {borderWidth, borderWidth, borderWidth, borderWidth}, 0); - - default: - return null; - } - } - - /* Value at Spacing.ALL index used for rounded borders, whole array used by rectangular borders */ - private @Nullable Spacing mBorderWidth; - private @Nullable Spacing mBorderRGB; - private @Nullable Spacing mBorderAlpha; - private @Nullable BorderStyle mBorderStyle; - - private @Nullable Path mInnerClipPathForBorderRadius; - private @Nullable Path mBackgroundColorRenderPath; - private @Nullable Path mOuterClipPathForBorderRadius; - private @Nullable Path mPathForBorderRadiusOutline; - private @Nullable Path mPathForBorder; - private final Path mPathForSingleBorder = new Path(); - private @Nullable Path mCenterDrawPath; - private @Nullable RectF mInnerClipTempRectForBorderRadius; - private @Nullable RectF mOuterClipTempRectForBorderRadius; - private @Nullable RectF mTempRectForBorderRadiusOutline; - private @Nullable RectF mTempRectForCenterDrawPath; - private @Nullable PointF mInnerTopLeftCorner; - private @Nullable PointF mInnerTopRightCorner; - private @Nullable PointF mInnerBottomRightCorner; - private @Nullable PointF mInnerBottomLeftCorner; - private boolean mNeedUpdatePathForBorderRadius = false; - - /* Used by all types of background and for drawing borders */ - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private int mColor = Color.TRANSPARENT; - private @Nullable List mBackgroundImageLayers = null; - private int mAlpha = 255; - - // There is a small gap between the edges of adjacent paths - // such as between the mBackgroundColorRenderPath and its border. - // The smallest amount (found to be 0.8f) is used to extend - // the paths, overlapping them and closing the visible gap. - private final float mGapBetweenPaths = 0.8f; - - private BorderRadiusStyle mBorderRadius = new BorderRadiusStyle(); - private ComputedBorderRadius mComputedBorderRadius = new ComputedBorderRadius(); - private final Context mContext; - - // Should be removed after migrating to Android layout direction. - private int mLayoutDirectionOverride = -1; - - public CSSBackgroundDrawable(Context context) { - mContext = context; - } - - @Override - public void draw(Canvas canvas) { - updatePathEffect(); - if (!hasRoundedBorders()) { - drawRectangularBackgroundWithBorders(canvas); - } else { - drawRoundedBackgroundWithBorders(canvas); - } - } - - public boolean hasRoundedBorders() { - return mBorderRadius.hasRoundedBorders(); - } - - @Override - protected void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); - mNeedUpdatePathForBorderRadius = true; - } - - @Override - public void setAlpha(int alpha) { - if (alpha != mAlpha) { - mAlpha = alpha; - invalidateSelf(); - } - } - - @Override - public int getAlpha() { - return mAlpha; - } - - @Override - public void setColorFilter(ColorFilter cf) { - // do nothing - } - - @Deprecated - public void setLayoutDirectionOverride(int layoutDirection) { - if (mLayoutDirectionOverride != layoutDirection) { - mLayoutDirectionOverride = layoutDirection; - } - } - - @Override - @SuppressLint("WrongConstant") - public int getLayoutDirection() { - return mLayoutDirectionOverride == -1 ? super.getLayoutDirection() : mLayoutDirectionOverride; - } - - @Override - public int getOpacity() { - int alpha = (Color.alpha(mColor) * mAlpha) >> 8; - switch (alpha) { - case 255: - return PixelFormat.OPAQUE; - case 0: - return PixelFormat.TRANSPARENT; - default: - return PixelFormat.TRANSLUCENT; - } - } - - /* Android's elevation implementation requires this to be implemented to know where to draw the shadow. */ - @Override - public void getOutline(Outline outline) { - if (hasRoundedBorders()) { - updatePath(); - - outline.setConvexPath(Preconditions.checkNotNull(mPathForBorderRadiusOutline)); - } else { - outline.setRect(getBounds()); - } - } - - public void setBorderWidth(int position, float width) { - if (mBorderWidth == null) { - mBorderWidth = new Spacing(); - } - if (!FloatUtil.floatsEqual(mBorderWidth.getRaw(position), width)) { - mBorderWidth.set(position, width); - switch (position) { - case Spacing.ALL: - case Spacing.LEFT: - case Spacing.BOTTOM: - case Spacing.RIGHT: - case Spacing.TOP: - case Spacing.START: - case Spacing.END: - mNeedUpdatePathForBorderRadius = true; - } - invalidateSelf(); - } - } - - public void setBorderColor(int position, @Nullable Integer color) { - float rgbComponent = color == null ? Float.NaN : (float) ((int) color & 0x00FFFFFF); - float alphaComponent = color == null ? Float.NaN : (float) ((int) color >>> 24); - - this.setBorderRGB(position, rgbComponent); - this.setBorderAlpha(position, alphaComponent); - mNeedUpdatePathForBorderRadius = true; - } - - private void setBorderRGB(int position, float rgb) { - // set RGB component - if (mBorderRGB == null) { - mBorderRGB = new Spacing(DEFAULT_BORDER_RGB); - } - if (!FloatUtil.floatsEqual(mBorderRGB.getRaw(position), rgb)) { - mBorderRGB.set(position, rgb); - invalidateSelf(); - } - } - - private void setBorderAlpha(int position, float alpha) { - // set Alpha component - if (mBorderAlpha == null) { - mBorderAlpha = new Spacing(DEFAULT_BORDER_ALPHA); - } - if (!FloatUtil.floatsEqual(mBorderAlpha.getRaw(position), alpha)) { - mBorderAlpha.set(position, alpha); - invalidateSelf(); - } - } - - public void setBorderStyle(@Nullable String style) { - BorderStyle borderStyle = - style == null ? null : BorderStyle.valueOf(style.toUpperCase(Locale.US)); - setBorderStyle(borderStyle); - } - - public void setBorderStyle(@Nullable BorderStyle borderStyle) { - if (mBorderStyle != borderStyle) { - mBorderStyle = borderStyle; - mNeedUpdatePathForBorderRadius = true; - invalidateSelf(); - } - } - - public @Nullable BorderStyle getBorderStyle() { - return mBorderStyle; - } - - /** - * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, LengthPercentage)} instead. - */ - @Deprecated(since = "0.75.0", forRemoval = true) - public void setRadius(float radius) { - @Nullable Float boxedRadius = Float.isNaN(radius) ? null : Float.valueOf(radius); - if (boxedRadius == null) { - setBorderRadius(BorderRadiusProp.BORDER_RADIUS, null); - } else { - setBorderRadius( - BorderRadiusProp.BORDER_RADIUS, - new LengthPercentage(boxedRadius, LengthPercentageType.POINT)); - } - } - - /** - * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, LengthPercentage)} instead. - */ - @Deprecated(since = "0.75.0", forRemoval = true) - public void setRadius(float radius, int position) { - @Nullable Float boxedRadius = Float.isNaN(radius) ? null : Float.valueOf(radius); - - if (boxedRadius == null) { - mBorderRadius.set(BorderRadiusProp.values()[position], null); - invalidateSelf(); - } else { - setBorderRadius( - BorderRadiusProp.values()[position], - new LengthPercentage(boxedRadius, LengthPercentageType.POINT)); - } - } - - public void setBorderRadius(BorderRadiusProp property, @Nullable LengthPercentage radius) { - if (!Objects.equals(radius, mBorderRadius.get(property))) { - mBorderRadius.set(property, radius); - mNeedUpdatePathForBorderRadius = true; - invalidateSelf(); - } - } - - public void setBorderRadius(BorderRadiusStyle radius) { - mBorderRadius = radius; - } - - public BorderRadiusStyle getBorderRadius() { - return mBorderRadius; - } - - // Here, "inner" refers to the border radius on the inside of the border. So - // it ends up being the "outer" border radius inset by the respective width. - public float getInnerBorderRadius(float computedRadius, float borderWidth) { - return Math.max(computedRadius - borderWidth, 0); - } - - public void setColor(int color) { - mColor = color; - invalidateSelf(); - } - - public void setBackgroundImage(@Nullable List backgroundImageLayers) { - mBackgroundImageLayers = backgroundImageLayers; - invalidateSelf(); - } - - public int getColor() { - return mColor; - } - - public @Nullable Path getBorderBoxPath() { - if (hasRoundedBorders()) { - updatePath(); - return new Path(Preconditions.checkNotNull(mOuterClipPathForBorderRadius)); - } - - return null; - } - - public RectF getBorderBoxRect() { - return new RectF(getBounds()); - } - - public @Nullable Path getPaddingBoxPath() { - if (hasRoundedBorders()) { - updatePath(); - return new Path(Preconditions.checkNotNull(mInnerClipPathForBorderRadius)); - } - - return null; - } - - public RectF getPaddingBoxRect() { - @Nullable RectF insets = getDirectionAwareBorderInsets(); - if (insets == null) { - return new RectF(0, 0, getBounds().width(), getBounds().height()); - } - - return new RectF( - insets.left, - insets.top, - getBounds().width() - insets.right, - getBounds().height() - insets.bottom); - } - - private void drawRoundedBackgroundWithBorders(Canvas canvas) { - updatePath(); - canvas.save(); - - // Draws the View without its border first (with background color fill) - int useColor = ColorUtils.setAlphaComponent(mColor, (Color.alpha(mColor) * mAlpha) >> 8); - if (Color.alpha(useColor) != 0) { - mPaint.setColor(useColor); - mPaint.setStyle(Paint.Style.FILL); - canvas.drawPath(Preconditions.checkNotNull(mBackgroundColorRenderPath), mPaint); - } - - if (mBackgroundImageLayers != null && !mBackgroundImageLayers.isEmpty()) { - mPaint.setShader(getBackgroundImageShader()); - mPaint.setStyle(Paint.Style.FILL); - canvas.drawPath(Preconditions.checkNotNull(mBackgroundColorRenderPath), mPaint); - mPaint.setShader(null); - } - - final RectF borderWidth = getDirectionAwareBorderInsets(); - int colorLeft = getBorderColor(Spacing.LEFT); - int colorTop = getBorderColor(Spacing.TOP); - int colorRight = getBorderColor(Spacing.RIGHT); - int colorBottom = getBorderColor(Spacing.BOTTOM); - - int colorBlock = getBorderColor(Spacing.BLOCK); - int colorBlockStart = getBorderColor(Spacing.BLOCK_START); - int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); - - if (isBorderColorDefined(Spacing.BLOCK)) { - colorBottom = colorBlock; - colorTop = colorBlock; - } - if (isBorderColorDefined(Spacing.BLOCK_END)) { - colorBottom = colorBlockEnd; - } - if (isBorderColorDefined(Spacing.BLOCK_START)) { - colorTop = colorBlockStart; - } - - if (borderWidth.top > 0 - || borderWidth.bottom > 0 - || borderWidth.left > 0 - || borderWidth.right > 0) { - - // Clip outer border - canvas.clipPath( - Preconditions.checkNotNull(mOuterClipPathForBorderRadius), Region.Op.INTERSECT); - - // If it's a full and even border draw inner rect path with stroke - final float fullBorderWidth = getFullBorderWidth(); - int borderColor = getBorderColor(Spacing.ALL); - if (borderWidth.top == fullBorderWidth - && borderWidth.bottom == fullBorderWidth - && borderWidth.left == fullBorderWidth - && borderWidth.right == fullBorderWidth - && colorLeft == borderColor - && colorTop == borderColor - && colorRight == borderColor - && colorBottom == borderColor) { - if (fullBorderWidth > 0) { - mPaint.setColor(multiplyColorAlpha(borderColor, mAlpha)); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(fullBorderWidth); - canvas.drawPath(Preconditions.checkNotNull(mCenterDrawPath), mPaint); - } - } - // In the case of uneven border widths/colors draw quadrilateral in each direction - else { - mPaint.setStyle(Paint.Style.FILL); - - // Clip inner border - canvas.clipPath( - Preconditions.checkNotNull(mInnerClipPathForBorderRadius), Region.Op.DIFFERENCE); - - final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - int colorStart = getBorderColor(Spacing.START); - int colorEnd = getBorderColor(Spacing.END); - - if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) { - if (!isBorderColorDefined(Spacing.START)) { - colorStart = colorLeft; - } - - if (!isBorderColorDefined(Spacing.END)) { - colorEnd = colorRight; - } - - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; - - colorLeft = directionAwareColorLeft; - colorRight = directionAwareColorRight; - } else { - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; - - final boolean isColorStartDefined = isBorderColorDefined(Spacing.START); - final boolean isColorEndDefined = isBorderColorDefined(Spacing.END); - final boolean isDirectionAwareColorLeftDefined = - isRTL ? isColorEndDefined : isColorStartDefined; - final boolean isDirectionAwareColorRightDefined = - isRTL ? isColorStartDefined : isColorEndDefined; - - if (isDirectionAwareColorLeftDefined) { - colorLeft = directionAwareColorLeft; - } - - if (isDirectionAwareColorRightDefined) { - colorRight = directionAwareColorRight; - } - } - - final RectF outerClipTempRect = - Preconditions.checkNotNull(mOuterClipTempRectForBorderRadius); - final float left = outerClipTempRect.left; - final float right = outerClipTempRect.right; - final float top = outerClipTempRect.top; - final float bottom = outerClipTempRect.bottom; - - final PointF innerTopLeftCorner = Preconditions.checkNotNull(mInnerTopLeftCorner); - final PointF innerTopRightCorner = Preconditions.checkNotNull(mInnerTopRightCorner); - final PointF innerBottomLeftCorner = Preconditions.checkNotNull(mInnerBottomLeftCorner); - final PointF innerBottomRightCorner = Preconditions.checkNotNull(mInnerBottomRightCorner); - - // mGapBetweenPaths is used to close the gap between the diagonal - // edges of the quadrilaterals on adjacent sides of the rectangle - if (borderWidth.left > 0) { - final float x1 = left; - final float y1 = top - mGapBetweenPaths; - final float x2 = innerTopLeftCorner.x; - final float y2 = innerTopLeftCorner.y - mGapBetweenPaths; - final float x3 = innerBottomLeftCorner.x; - final float y3 = innerBottomLeftCorner.y + mGapBetweenPaths; - final float x4 = left; - final float y4 = bottom + mGapBetweenPaths; - - drawQuadrilateral(canvas, colorLeft, x1, y1, x2, y2, x3, y3, x4, y4); - } - - if (borderWidth.top > 0) { - final float x1 = left - mGapBetweenPaths; - final float y1 = top; - final float x2 = innerTopLeftCorner.x - mGapBetweenPaths; - final float y2 = innerTopLeftCorner.y; - final float x3 = innerTopRightCorner.x + mGapBetweenPaths; - final float y3 = innerTopRightCorner.y; - final float x4 = right + mGapBetweenPaths; - final float y4 = top; - - drawQuadrilateral(canvas, colorTop, x1, y1, x2, y2, x3, y3, x4, y4); - } - - if (borderWidth.right > 0) { - final float x1 = right; - final float y1 = top - mGapBetweenPaths; - final float x2 = innerTopRightCorner.x; - final float y2 = innerTopRightCorner.y - mGapBetweenPaths; - final float x3 = innerBottomRightCorner.x; - final float y3 = innerBottomRightCorner.y + mGapBetweenPaths; - final float x4 = right; - final float y4 = bottom + mGapBetweenPaths; - - drawQuadrilateral(canvas, colorRight, x1, y1, x2, y2, x3, y3, x4, y4); - } - - if (borderWidth.bottom > 0) { - final float x1 = left - mGapBetweenPaths; - final float y1 = bottom; - final float x2 = innerBottomLeftCorner.x - mGapBetweenPaths; - final float y2 = innerBottomLeftCorner.y; - final float x3 = innerBottomRightCorner.x + mGapBetweenPaths; - final float y3 = innerBottomRightCorner.y; - final float x4 = right + mGapBetweenPaths; - final float y4 = bottom; - - drawQuadrilateral(canvas, colorBottom, x1, y1, x2, y2, x3, y3, x4, y4); - } - } - } - - canvas.restore(); - } - - private void updatePath() { - if (!mNeedUpdatePathForBorderRadius) { - return; - } - - mNeedUpdatePathForBorderRadius = false; - - if (mInnerClipPathForBorderRadius == null) { - mInnerClipPathForBorderRadius = new Path(); - } - - if (mBackgroundColorRenderPath == null) { - mBackgroundColorRenderPath = new Path(); - } - - if (mOuterClipPathForBorderRadius == null) { - mOuterClipPathForBorderRadius = new Path(); - } - - if (mPathForBorderRadiusOutline == null) { - mPathForBorderRadiusOutline = new Path(); - } - - if (mCenterDrawPath == null) { - mCenterDrawPath = new Path(); - } - - if (mInnerClipTempRectForBorderRadius == null) { - mInnerClipTempRectForBorderRadius = new RectF(); - } - - if (mOuterClipTempRectForBorderRadius == null) { - mOuterClipTempRectForBorderRadius = new RectF(); - } - - if (mTempRectForBorderRadiusOutline == null) { - mTempRectForBorderRadiusOutline = new RectF(); - } - - if (mTempRectForCenterDrawPath == null) { - mTempRectForCenterDrawPath = new RectF(); - } - - mInnerClipPathForBorderRadius.reset(); - mBackgroundColorRenderPath.reset(); - mOuterClipPathForBorderRadius.reset(); - mPathForBorderRadiusOutline.reset(); - mCenterDrawPath.reset(); - - mInnerClipTempRectForBorderRadius.set(getBounds()); - mOuterClipTempRectForBorderRadius.set(getBounds()); - mTempRectForBorderRadiusOutline.set(getBounds()); - mTempRectForCenterDrawPath.set(getBounds()); - - final RectF borderWidth = getDirectionAwareBorderInsets(); - - int colorLeft = getBorderColor(Spacing.LEFT); - int colorTop = getBorderColor(Spacing.TOP); - int colorRight = getBorderColor(Spacing.RIGHT); - int colorBottom = getBorderColor(Spacing.BOTTOM); - int borderColor = getBorderColor(Spacing.ALL); - - int colorBlock = getBorderColor(Spacing.BLOCK); - int colorBlockStart = getBorderColor(Spacing.BLOCK_START); - int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); - - if (isBorderColorDefined(Spacing.BLOCK)) { - colorBottom = colorBlock; - colorTop = colorBlock; - } - if (isBorderColorDefined(Spacing.BLOCK_END)) { - colorBottom = colorBlockEnd; - } - if (isBorderColorDefined(Spacing.BLOCK_START)) { - colorTop = colorBlockStart; - } - - // Clip border ONLY if at least one edge is non-transparent - if (Color.alpha(colorLeft) != 0 - || Color.alpha(colorTop) != 0 - || Color.alpha(colorRight) != 0 - || Color.alpha(colorBottom) != 0 - || Color.alpha(borderColor) != 0) { - - mInnerClipTempRectForBorderRadius.top += borderWidth.top; - mInnerClipTempRectForBorderRadius.bottom -= borderWidth.bottom; - mInnerClipTempRectForBorderRadius.left += borderWidth.left; - mInnerClipTempRectForBorderRadius.right -= borderWidth.right; - } - - mTempRectForCenterDrawPath.top += borderWidth.top * 0.5f; - mTempRectForCenterDrawPath.bottom -= borderWidth.bottom * 0.5f; - mTempRectForCenterDrawPath.left += borderWidth.left * 0.5f; - mTempRectForCenterDrawPath.right -= borderWidth.right * 0.5f; - - mComputedBorderRadius = - mBorderRadius.resolve( - getLayoutDirection(), - mContext, - PixelUtil.toDIPFromPixel(mOuterClipTempRectForBorderRadius.width()), - PixelUtil.toDIPFromPixel(mOuterClipTempRectForBorderRadius.height())); - CornerRadii topLeftRadius = mComputedBorderRadius.getTopLeft().toPixelFromDIP(); - CornerRadii topRightRadius = mComputedBorderRadius.getTopRight().toPixelFromDIP(); - CornerRadii bottomLeftRadius = mComputedBorderRadius.getBottomLeft().toPixelFromDIP(); - CornerRadii bottomRightRadius = mComputedBorderRadius.getBottomRight().toPixelFromDIP(); - - final float innerTopLeftRadiusX = - getInnerBorderRadius(topLeftRadius.getHorizontal(), borderWidth.left); - final float innerTopLeftRadiusY = - getInnerBorderRadius(topLeftRadius.getVertical(), borderWidth.top); - final float innerTopRightRadiusX = - getInnerBorderRadius(topRightRadius.getHorizontal(), borderWidth.right); - final float innerTopRightRadiusY = - getInnerBorderRadius(topRightRadius.getVertical(), borderWidth.top); - final float innerBottomRightRadiusX = - getInnerBorderRadius(bottomRightRadius.getHorizontal(), borderWidth.right); - final float innerBottomRightRadiusY = - getInnerBorderRadius(bottomRightRadius.getVertical(), borderWidth.bottom); - final float innerBottomLeftRadiusX = - getInnerBorderRadius(bottomLeftRadius.getHorizontal(), borderWidth.left); - final float innerBottomLeftRadiusY = - getInnerBorderRadius(bottomLeftRadius.getVertical(), borderWidth.bottom); - - mInnerClipPathForBorderRadius.addRoundRect( - mInnerClipTempRectForBorderRadius, - new float[] { - innerTopLeftRadiusX, - innerTopLeftRadiusY, - innerTopRightRadiusX, - innerTopRightRadiusY, - innerBottomRightRadiusX, - innerBottomRightRadiusY, - innerBottomLeftRadiusX, - innerBottomLeftRadiusY, - }, - Path.Direction.CW); - - // There is a small gap between mBackgroundColorRenderPath and its - // border. mGapBetweenPaths is used to slightly enlarge the rectangle - // (mInnerClipTempRectForBorderRadius), ensuring the border can be - // drawn on top without the gap. - // only close gap between border and main path if we draw the border, otherwise - // we wind up pixelating small pixel-radius curves - mBackgroundColorRenderPath.addRoundRect( - (borderWidth.left > 0) - ? mInnerClipTempRectForBorderRadius.left - mGapBetweenPaths - : mInnerClipTempRectForBorderRadius.left, - (borderWidth.top > 0) - ? mInnerClipTempRectForBorderRadius.top - mGapBetweenPaths - : mInnerClipTempRectForBorderRadius.top, - (borderWidth.right > 0) - ? mInnerClipTempRectForBorderRadius.right + mGapBetweenPaths - : mInnerClipTempRectForBorderRadius.right, - (borderWidth.bottom > 0) - ? mInnerClipTempRectForBorderRadius.bottom + mGapBetweenPaths - : mInnerClipTempRectForBorderRadius.bottom, - new float[] { - innerTopLeftRadiusX, - innerTopLeftRadiusY, - innerTopRightRadiusX, - innerTopRightRadiusY, - innerBottomRightRadiusX, - innerBottomRightRadiusY, - innerBottomLeftRadiusX, - innerBottomLeftRadiusY, - }, - Path.Direction.CW); - - mOuterClipPathForBorderRadius.addRoundRect( - mOuterClipTempRectForBorderRadius, - new float[] { - topLeftRadius.getHorizontal(), - topLeftRadius.getVertical(), - topRightRadius.getHorizontal(), - topRightRadius.getVertical(), - bottomRightRadius.getHorizontal(), - bottomRightRadius.getVertical(), - bottomLeftRadius.getHorizontal(), - bottomLeftRadius.getVertical() - }, - Path.Direction.CW); - - float extraRadiusForOutline = 0; - - if (mBorderWidth != null) { - extraRadiusForOutline = mBorderWidth.get(Spacing.ALL) / 2f; - } - - mPathForBorderRadiusOutline.addRoundRect( - mTempRectForBorderRadiusOutline, - new float[] { - topLeftRadius.getHorizontal() + extraRadiusForOutline, - topLeftRadius.getVertical() + extraRadiusForOutline, - topRightRadius.getHorizontal() + extraRadiusForOutline, - topRightRadius.getVertical() + extraRadiusForOutline, - bottomRightRadius.getHorizontal() + extraRadiusForOutline, - bottomRightRadius.getVertical() + extraRadiusForOutline, - bottomLeftRadius.getHorizontal() + extraRadiusForOutline, - bottomLeftRadius.getVertical() + extraRadiusForOutline - }, - Path.Direction.CW); - - mCenterDrawPath.addRoundRect( - mTempRectForCenterDrawPath, - new float[] { - topLeftRadius.getHorizontal() - borderWidth.left * 0.5f, - topLeftRadius.getVertical() - borderWidth.top * 0.5f, - topRightRadius.getHorizontal() - borderWidth.right * 0.5f, - topRightRadius.getVertical() - borderWidth.top * 0.5f, - bottomRightRadius.getHorizontal() - borderWidth.right * 0.5f, - bottomRightRadius.getVertical() - borderWidth.bottom * 0.5f, - bottomLeftRadius.getHorizontal() - borderWidth.left * 0.5f, - bottomLeftRadius.getVertical() - borderWidth.bottom * 0.5f, - }, - Path.Direction.CW); - - /** - * Rounded Multi-Colored Border Algorithm: - * - *

Let O (for outer) = (top, left, bottom, right) be the rectangle that represents the size - * and position of a view V. Since the box-sizing of all React Native views is border-box, any - * border of V will render inside O. - * - *

Let BorderWidth = (borderTop, borderLeft, borderBottom, borderRight). - * - *

Let I (for inner) = O - BorderWidth. - * - *

Then, remembering that O and I are rectangles and that I is inside O, O - I gives us the - * border of V. Therefore, we can use canvas.clipPath to draw V's border. - * - *

canvas.clipPath(O, Region.OP.INTERSECT); - * - *

canvas.clipPath(I, Region.OP.DIFFERENCE); - * - *

canvas.drawRect(O, paint); - * - *

This lets us draw non-rounded single-color borders. - * - *

To extend this algorithm to rounded single-color borders, we: - * - *

1. Curve the corners of O by the (border radii of V) using Path#addRoundRect. - * - *

2. Curve the corners of I by (border radii of V - border widths of V) using - * Path#addRoundRect. - * - *

Let O' = curve(O, border radii of V). - * - *

Let I' = curve(I, border radii of V - border widths of V) - * - *

The rationale behind this decision is the (first sentence of the) following section in the - * CSS Backgrounds and Borders Module Level 3: - * https://www.w3.org/TR/css3-background/#the-border-radius. - * - *

After both O and I have been curved, we can execute the following lines once again to - * render curved single-color borders: - * - *

canvas.clipPath(O, Region.OP.INTERSECT); - * - *

canvas.clipPath(I, Region.OP.DIFFERENCE); - * - *

canvas.drawRect(O, paint); - * - *

To extend this algorithm to rendering multi-colored rounded borders, we render each side - * of the border as its own quadrilateral. Suppose that we were handling the case where all the - * border radii are 0. Then, the four quadrilaterals would be: - * - *

Left: (O.left, O.top), (I.left, I.top), (I.left, I.bottom), (O.left, O.bottom) - * - *

Top: (O.left, O.top), (I.left, I.top), (I.right, I.top), (O.right, O.top) - * - *

Right: (O.right, O.top), (I.right, I.top), (I.right, I.bottom), (O.right, O.bottom) - * - *

Bottom: (O.right, O.bottom), (I.right, I.bottom), (I.left, I.bottom), (O.left, O.bottom) - * - *

Now, lets consider what happens when we render a rounded border (radii != 0). For the sake - * of simplicity, let's focus on the top edge of the Left border: - * - *

Let borderTopLeftRadius = 5. Let borderLeftWidth = 1. Let borderTopWidth = 2. - * - *

We know that O is curved by the ellipse E_O (a = 5, b = 5). We know that I is curved by - * the ellipse E_I (a = 5 - 1, b = 5 - 2). - * - *

Since we have clipping, it should be safe to set the top-left point of the Left - * quadrilateral's top edge to (O.left, O.top). - * - *

But, what should the top-right point be? - * - *

The fact that the border is curved shouldn't change the slope (nor the position) of the - * line connecting the top-left and top-right points of the Left quadrilateral's top edge. - * Therefore, The top-right point should lie somewhere on the line L = (1 - a) * (O.left, O.top) - * + a * (I.left, I.top). - * - *

a != 0, because then the top-left and top-right points would be the same and - * borderLeftWidth = 1. a != 1, because then the top-right point would not touch an edge of the - * ellipse E_I. We want the top-right point to touch an edge of the inner ellipse because the - * border curves with E_I on the top-left corner of V. - * - *

Therefore, it must be the case that a > 1. Two natural locations of the top-right point - * exist: 1. The first intersection of L with E_I. 2. The second intersection of L with E_I. - * - *

We choose the top-right point of the top edge of the Left quadrilateral to be an arbitrary - * intersection of L with E_I. - */ - if (mInnerTopLeftCorner == null) { - mInnerTopLeftCorner = new PointF(); - } - - /** Compute mInnerTopLeftCorner */ - mInnerTopLeftCorner.x = mInnerClipTempRectForBorderRadius.left; - mInnerTopLeftCorner.y = mInnerClipTempRectForBorderRadius.top; - - getEllipseIntersectionWithLine( - // Ellipse Bounds - mInnerClipTempRectForBorderRadius.left, - mInnerClipTempRectForBorderRadius.top, - mInnerClipTempRectForBorderRadius.left + 2 * innerTopLeftRadiusX, - mInnerClipTempRectForBorderRadius.top + 2 * innerTopLeftRadiusY, - - // Line Start - mOuterClipTempRectForBorderRadius.left, - mOuterClipTempRectForBorderRadius.top, - - // Line End - mInnerClipTempRectForBorderRadius.left, - mInnerClipTempRectForBorderRadius.top, - - // Result - mInnerTopLeftCorner); - - /** Compute mInnerBottomLeftCorner */ - if (mInnerBottomLeftCorner == null) { - mInnerBottomLeftCorner = new PointF(); - } - - mInnerBottomLeftCorner.x = mInnerClipTempRectForBorderRadius.left; - mInnerBottomLeftCorner.y = mInnerClipTempRectForBorderRadius.bottom; - - getEllipseIntersectionWithLine( - // Ellipse Bounds - mInnerClipTempRectForBorderRadius.left, - mInnerClipTempRectForBorderRadius.bottom - 2 * innerBottomLeftRadiusY, - mInnerClipTempRectForBorderRadius.left + 2 * innerBottomLeftRadiusX, - mInnerClipTempRectForBorderRadius.bottom, - - // Line Start - mOuterClipTempRectForBorderRadius.left, - mOuterClipTempRectForBorderRadius.bottom, - - // Line End - mInnerClipTempRectForBorderRadius.left, - mInnerClipTempRectForBorderRadius.bottom, - - // Result - mInnerBottomLeftCorner); - - /** Compute mInnerTopRightCorner */ - if (mInnerTopRightCorner == null) { - mInnerTopRightCorner = new PointF(); - } - - mInnerTopRightCorner.x = mInnerClipTempRectForBorderRadius.right; - mInnerTopRightCorner.y = mInnerClipTempRectForBorderRadius.top; - - getEllipseIntersectionWithLine( - // Ellipse Bounds - mInnerClipTempRectForBorderRadius.right - 2 * innerTopRightRadiusX, - mInnerClipTempRectForBorderRadius.top, - mInnerClipTempRectForBorderRadius.right, - mInnerClipTempRectForBorderRadius.top + 2 * innerTopRightRadiusY, - - // Line Start - mOuterClipTempRectForBorderRadius.right, - mOuterClipTempRectForBorderRadius.top, - - // Line End - mInnerClipTempRectForBorderRadius.right, - mInnerClipTempRectForBorderRadius.top, - - // Result - mInnerTopRightCorner); - - /** Compute mInnerBottomRightCorner */ - if (mInnerBottomRightCorner == null) { - mInnerBottomRightCorner = new PointF(); - } - - mInnerBottomRightCorner.x = mInnerClipTempRectForBorderRadius.right; - mInnerBottomRightCorner.y = mInnerClipTempRectForBorderRadius.bottom; - - getEllipseIntersectionWithLine( - // Ellipse Bounds - mInnerClipTempRectForBorderRadius.right - 2 * innerBottomRightRadiusX, - mInnerClipTempRectForBorderRadius.bottom - 2 * innerBottomRightRadiusY, - mInnerClipTempRectForBorderRadius.right, - mInnerClipTempRectForBorderRadius.bottom, - - // Line Start - mOuterClipTempRectForBorderRadius.right, - mOuterClipTempRectForBorderRadius.bottom, - - // Line End - mInnerClipTempRectForBorderRadius.right, - mInnerClipTempRectForBorderRadius.bottom, - - // Result - mInnerBottomRightCorner); - } - - private static void getEllipseIntersectionWithLine( - double ellipseBoundsLeft, - double ellipseBoundsTop, - double ellipseBoundsRight, - double ellipseBoundsBottom, - double lineStartX, - double lineStartY, - double lineEndX, - double lineEndY, - PointF result) { - final double ellipseCenterX = (ellipseBoundsLeft + ellipseBoundsRight) / 2; - final double ellipseCenterY = (ellipseBoundsTop + ellipseBoundsBottom) / 2; - - /** - * Step 1: - * - *

Translate the line so that the ellipse is at the origin. - * - *

Why? It makes the math easier by changing the ellipse equation from ((x - - * ellipseCenterX)/a)^2 + ((y - ellipseCenterY)/b)^2 = 1 to (x/a)^2 + (y/b)^2 = 1. - */ - lineStartX -= ellipseCenterX; - lineStartY -= ellipseCenterY; - lineEndX -= ellipseCenterX; - lineEndY -= ellipseCenterY; - - /** - * Step 2: - * - *

Ellipse equation: (x/a)^2 + (y/b)^2 = 1 Line equation: y = mx + c - */ - final double a = Math.abs(ellipseBoundsRight - ellipseBoundsLeft) / 2; - final double b = Math.abs(ellipseBoundsBottom - ellipseBoundsTop) / 2; - final double m = (lineEndY - lineStartY) / (lineEndX - lineStartX); - final double c = lineStartY - m * lineStartX; // Just a point on the line - - /** - * Step 3: - * - *

Substitute the Line equation into the Ellipse equation. Solve for x. Eventually, you'll - * have to use the quadratic formula. - * - *

Quadratic formula: Ax^2 + Bx + C = 0 - */ - final double A = (b * b + a * a * m * m); - final double B = 2 * a * a * c * m; - final double C = (a * a * (c * c - b * b)); - - /** - * Step 4: - * - *

Apply Quadratic formula. D = determinant / 2A - */ - final double D = Math.sqrt(-C / A + Math.pow(B / (2 * A), 2)); - final double x2 = -B / (2 * A) - D; - final double y2 = m * x2 + c; - - /** - * Step 5: - * - *

Undo the space transformation in Step 5. - */ - final double x = x2 + ellipseCenterX; - final double y = y2 + ellipseCenterY; - - if (!Double.isNaN(x) && !Double.isNaN(y)) { - result.x = (float) x; - result.y = (float) y; - } - } - - public float getBorderWidthOrDefaultTo(final float defaultValue, final int spacingType) { - @Nullable Float width = getBorderWidth(spacingType); - if (width == null) { - return defaultValue; - } - - return width; - } - - public @Nullable Float getBorderWidth(int spacingType) { - if (mBorderWidth == null) { - return null; - } - - final float width = mBorderWidth.getRaw(spacingType); - - if (Float.isNaN(width)) { - return null; - } - - return width; - } - - /** Set type of border */ - private void updatePathEffect() { - // Used for rounded border and rounded background - PathEffect mPathEffectForBorderStyle = - mBorderStyle != null ? getPathEffect(mBorderStyle, getFullBorderWidth()) : null; - - mPaint.setPathEffect(mPathEffectForBorderStyle); - } - - private void updatePathEffect(int borderWidth) { - PathEffect pathEffectForBorderStyle = null; - if (mBorderStyle != null) { - pathEffectForBorderStyle = getPathEffect(mBorderStyle, borderWidth); - } - mPaint.setPathEffect(pathEffectForBorderStyle); - } - - /** For rounded borders we use default "borderWidth" property. */ - public float getFullBorderWidth() { - return (mBorderWidth != null && !Float.isNaN(mBorderWidth.getRaw(Spacing.ALL))) - ? mBorderWidth.getRaw(Spacing.ALL) - : 0f; - } - - /** - * Quickly determine if all the set border colors are equal. Bitwise AND all the set colors - * together, then OR them all together. If the AND and the OR are the same, then the colors are - * compatible, so return this color. - * - *

Used to avoid expensive path creation and expensive calls to canvas.drawPath - * - * @return A compatible border color, or zero if the border colors are not compatible. - */ - private static int fastBorderCompatibleColorOrZero( - int borderLeft, - int borderTop, - int borderRight, - int borderBottom, - int colorLeft, - int colorTop, - int colorRight, - int colorBottom) { - int andSmear = - (borderLeft > 0 ? colorLeft : ALL_BITS_SET) - & (borderTop > 0 ? colorTop : ALL_BITS_SET) - & (borderRight > 0 ? colorRight : ALL_BITS_SET) - & (borderBottom > 0 ? colorBottom : ALL_BITS_SET); - int orSmear = - (borderLeft > 0 ? colorLeft : ALL_BITS_UNSET) - | (borderTop > 0 ? colorTop : ALL_BITS_UNSET) - | (borderRight > 0 ? colorRight : ALL_BITS_UNSET) - | (borderBottom > 0 ? colorBottom : ALL_BITS_UNSET); - return andSmear == orSmear ? andSmear : 0; - } - - private void drawRectangularBackgroundWithBorders(Canvas canvas) { - mPaint.setStyle(Paint.Style.FILL); - - int useColor = multiplyColorAlpha(mColor, mAlpha); - if (Color.alpha(useColor) != 0) { - mPaint.setColor(useColor); - canvas.drawRect(getBounds(), mPaint); - } - - if (mBackgroundImageLayers != null && !mBackgroundImageLayers.isEmpty()) { - mPaint.setShader(getBackgroundImageShader()); - canvas.drawRect(getBounds(), mPaint); - mPaint.setShader(null); - } - - final RectF borderWidth = getDirectionAwareBorderInsets(); - - final int borderLeft = Math.round(borderWidth.left); - final int borderTop = Math.round(borderWidth.top); - final int borderRight = Math.round(borderWidth.right); - final int borderBottom = Math.round(borderWidth.bottom); - - // maybe draw borders? - if (borderLeft > 0 || borderRight > 0 || borderTop > 0 || borderBottom > 0) { - Rect bounds = getBounds(); - - int colorLeft = getBorderColor(Spacing.LEFT); - int colorTop = getBorderColor(Spacing.TOP); - int colorRight = getBorderColor(Spacing.RIGHT); - int colorBottom = getBorderColor(Spacing.BOTTOM); - - int colorBlock = getBorderColor(Spacing.BLOCK); - int colorBlockStart = getBorderColor(Spacing.BLOCK_START); - int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); - - if (isBorderColorDefined(Spacing.BLOCK)) { - colorBottom = colorBlock; - colorTop = colorBlock; - } - if (isBorderColorDefined(Spacing.BLOCK_END)) { - colorBottom = colorBlockEnd; - } - if (isBorderColorDefined(Spacing.BLOCK_START)) { - colorTop = colorBlockStart; - } - - final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - int colorStart = getBorderColor(Spacing.START); - int colorEnd = getBorderColor(Spacing.END); - - if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) { - if (!isBorderColorDefined(Spacing.START)) { - colorStart = colorLeft; - } - - if (!isBorderColorDefined(Spacing.END)) { - colorEnd = colorRight; - } - - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; - - colorLeft = directionAwareColorLeft; - colorRight = directionAwareColorRight; - } else { - final int directionAwareColorLeft = isRTL ? colorEnd : colorStart; - final int directionAwareColorRight = isRTL ? colorStart : colorEnd; - - final boolean isColorStartDefined = isBorderColorDefined(Spacing.START); - final boolean isColorEndDefined = isBorderColorDefined(Spacing.END); - final boolean isDirectionAwareColorLeftDefined = - isRTL ? isColorEndDefined : isColorStartDefined; - final boolean isDirectionAwareColorRightDefined = - isRTL ? isColorStartDefined : isColorEndDefined; - - if (isDirectionAwareColorLeftDefined) { - colorLeft = directionAwareColorLeft; - } - - if (isDirectionAwareColorRightDefined) { - colorRight = directionAwareColorRight; - } - } - - int left = bounds.left; - int top = bounds.top; - - // Check for fast path to border drawing. - int fastBorderColor = - fastBorderCompatibleColorOrZero( - borderLeft, - borderTop, - borderRight, - borderBottom, - colorLeft, - colorTop, - colorRight, - colorBottom); - - if (fastBorderColor != 0) { - if (Color.alpha(fastBorderColor) != 0) { - // Border color is not transparent. - int right = bounds.right; - int bottom = bounds.bottom; - - mPaint.setColor(fastBorderColor); - mPaint.setStyle(Paint.Style.STROKE); - if (borderLeft > 0) { - mPathForSingleBorder.reset(); - int width = Math.round(borderWidth.left); - updatePathEffect(width); - mPaint.setStrokeWidth(width); - mPathForSingleBorder.moveTo(left + width / 2, top); - mPathForSingleBorder.lineTo(left + width / 2, bottom); - canvas.drawPath(mPathForSingleBorder, mPaint); - } - if (borderTop > 0) { - mPathForSingleBorder.reset(); - int width = Math.round(borderWidth.top); - updatePathEffect(width); - mPaint.setStrokeWidth(width); - mPathForSingleBorder.moveTo(left, top + width / 2); - mPathForSingleBorder.lineTo(right, top + width / 2); - canvas.drawPath(mPathForSingleBorder, mPaint); - } - if (borderRight > 0) { - mPathForSingleBorder.reset(); - int width = Math.round(borderWidth.right); - updatePathEffect(width); - mPaint.setStrokeWidth(width); - mPathForSingleBorder.moveTo(right - width / 2, top); - mPathForSingleBorder.lineTo(right - width / 2, bottom); - canvas.drawPath(mPathForSingleBorder, mPaint); - } - if (borderBottom > 0) { - mPathForSingleBorder.reset(); - int width = Math.round(borderWidth.bottom); - updatePathEffect(width); - mPaint.setStrokeWidth(width); - mPathForSingleBorder.moveTo(left, bottom - width / 2); - mPathForSingleBorder.lineTo(right, bottom - width / 2); - canvas.drawPath(mPathForSingleBorder, mPaint); - } - } - } else { - // If the path drawn previously is of the same color, - // there would be a slight white space between borders - // with anti-alias set to true. - // Therefore we need to disable anti-alias, and - // after drawing is done, we will re-enable it. - - mPaint.setAntiAlias(false); - - int width = bounds.width(); - int height = bounds.height(); - - if (borderLeft > 0) { - final float x1 = left; - final float y1 = top; - final float x2 = left + borderLeft; - final float y2 = top + borderTop; - final float x3 = left + borderLeft; - final float y3 = top + height - borderBottom; - final float x4 = left; - final float y4 = top + height; - - drawQuadrilateral(canvas, colorLeft, x1, y1, x2, y2, x3, y3, x4, y4); - } - - if (borderTop > 0) { - final float x1 = left; - final float y1 = top; - final float x2 = left + borderLeft; - final float y2 = top + borderTop; - final float x3 = left + width - borderRight; - final float y3 = top + borderTop; - final float x4 = left + width; - final float y4 = top; - - drawQuadrilateral(canvas, colorTop, x1, y1, x2, y2, x3, y3, x4, y4); - } - - if (borderRight > 0) { - final float x1 = left + width; - final float y1 = top; - final float x2 = left + width; - final float y2 = top + height; - final float x3 = left + width - borderRight; - final float y3 = top + height - borderBottom; - final float x4 = left + width - borderRight; - final float y4 = top + borderTop; - - drawQuadrilateral(canvas, colorRight, x1, y1, x2, y2, x3, y3, x4, y4); - } - - if (borderBottom > 0) { - final float x1 = left; - final float y1 = top + height; - final float x2 = left + width; - final float y2 = top + height; - final float x3 = left + width - borderRight; - final float y3 = top + height - borderBottom; - final float x4 = left + borderLeft; - final float y4 = top + height - borderBottom; - - drawQuadrilateral(canvas, colorBottom, x1, y1, x2, y2, x3, y3, x4, y4); - } - - // re-enable anti alias - mPaint.setAntiAlias(true); - } - } - } - - private void drawQuadrilateral( - Canvas canvas, - int fillColor, - float x1, - float y1, - float x2, - float y2, - float x3, - float y3, - float x4, - float y4) { - if (fillColor == Color.TRANSPARENT) { - return; - } - - if (mPathForBorder == null) { - mPathForBorder = new Path(); - } - - mPaint.setColor(fillColor); - mPathForBorder.reset(); - mPathForBorder.moveTo(x1, y1); - mPathForBorder.lineTo(x2, y2); - mPathForBorder.lineTo(x3, y3); - mPathForBorder.lineTo(x4, y4); - mPathForBorder.lineTo(x1, y1); - canvas.drawPath(mPathForBorder, mPaint); - } - - private static int colorFromAlphaAndRGBComponents(float alpha, float rgb) { - int rgbComponent = 0x00FFFFFF & (int) rgb; - int alphaComponent = 0xFF000000 & ((int) alpha) << 24; - - return rgbComponent | alphaComponent; - } - - private boolean isBorderColorDefined(int position) { - final float rgb = mBorderRGB != null ? mBorderRGB.get(position) : Float.NaN; - final float alpha = mBorderAlpha != null ? mBorderAlpha.get(position) : Float.NaN; - return !Float.isNaN(rgb) && !Float.isNaN(alpha); - } - - public int getBorderColor(int position) { - float rgb = mBorderRGB != null ? mBorderRGB.get(position) : DEFAULT_BORDER_RGB; - float alpha = mBorderAlpha != null ? mBorderAlpha.get(position) : DEFAULT_BORDER_ALPHA; - - return CSSBackgroundDrawable.colorFromAlphaAndRGBComponents(alpha, rgb); - } - - public RectF getDirectionAwareBorderInsets() { - final float borderWidth = getBorderWidthOrDefaultTo(0, Spacing.ALL); - final float borderTopWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.TOP); - final float borderBottomWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.BOTTOM); - float borderLeftWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.LEFT); - float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT); - - if (mBorderWidth != null) { - final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - float borderStartWidth = mBorderWidth.getRaw(Spacing.START); - float borderEndWidth = mBorderWidth.getRaw(Spacing.END); - - if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)) { - if (Float.isNaN(borderStartWidth)) { - borderStartWidth = borderLeftWidth; - } - - if (Float.isNaN(borderEndWidth)) { - borderEndWidth = borderRightWidth; - } - - final float directionAwareBorderLeftWidth = isRTL ? borderEndWidth : borderStartWidth; - final float directionAwareBorderRightWidth = isRTL ? borderStartWidth : borderEndWidth; - - borderLeftWidth = directionAwareBorderLeftWidth; - borderRightWidth = directionAwareBorderRightWidth; - } else { - final float directionAwareBorderLeftWidth = isRTL ? borderEndWidth : borderStartWidth; - final float directionAwareBorderRightWidth = isRTL ? borderStartWidth : borderEndWidth; - - if (!Float.isNaN(directionAwareBorderLeftWidth)) { - borderLeftWidth = directionAwareBorderLeftWidth; - } - - if (!Float.isNaN(directionAwareBorderRightWidth)) { - borderRightWidth = directionAwareBorderRightWidth; - } - } - } - - return new RectF(borderLeftWidth, borderTopWidth, borderRightWidth, borderBottomWidth); - } - - private @Nullable Shader getBackgroundImageShader() { - if (mBackgroundImageLayers == null) { - return null; - } - - Shader compositeShader = null; - for (BackgroundImageLayer backgroundImageLayer : mBackgroundImageLayers) { - Shader currentShader = backgroundImageLayer.getShader(getBounds()); - if (currentShader == null) { - continue; - } - if (compositeShader == null) { - compositeShader = currentShader; - } else { - compositeShader = - new ComposeShader(currentShader, compositeShader, PorterDuff.Mode.SRC_OVER); - } - } - return compositeShader; - } - - /** - * Multiplies the color with the given alpha. - * - * @param color color to be multiplied - * @param alpha value between 0 and 255 - * @return multiplied color - */ - private static int multiplyColorAlpha(int color, int alpha) { - if (alpha == 255) { - return color; - } - if (alpha == 0) { - return color & 0x00FFFFFF; - } - alpha = alpha + (alpha >> 7); // make it 0..256 - int colorAlpha = color >>> 24; - int multipliedAlpha = colorAlpha * alpha >> 8; - return (multipliedAlpha << 24) | (color & 0x00FFFFFF); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt index ad8f83b887e2..06acd3acaa07 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CompositeBackgroundDrawable.kt @@ -35,14 +35,6 @@ internal class CompositeBackgroundDrawable( /** Non-inset box shadows */ val outerShadows: List = emptyList(), - /** - * CSS background layer and border rendering - * - * TODO: we should extract path logic from here, and fast-path to using simpler drawables like - * ColorDrawable in the common cases - */ - val cssBackground: CSSBackgroundDrawable? = null, - /** Background rendering Layer */ val background: BackgroundDrawable? = null, @@ -68,7 +60,6 @@ internal class CompositeBackgroundDrawable( createLayersArray( originalBackground, outerShadows, - cssBackground, background, border, feedbackUnderlay, @@ -84,28 +75,11 @@ internal class CompositeBackgroundDrawable( setPaddingMode(LayerDrawable.PADDING_MODE_STACK) } - fun withNewCssBackground(cssBackground: CSSBackgroundDrawable?): CompositeBackgroundDrawable { - return CompositeBackgroundDrawable( - context, - originalBackground, - outerShadows, - cssBackground, - background, - border, - feedbackUnderlay, - innerShadows, - outline, - borderInsets, - borderRadius, - ) - } - fun withNewBackground(background: BackgroundDrawable?): CompositeBackgroundDrawable { return CompositeBackgroundDrawable( context, originalBackground, outerShadows, - cssBackground, background, border, feedbackUnderlay, @@ -124,7 +98,6 @@ internal class CompositeBackgroundDrawable( context, originalBackground, outerShadows, - cssBackground, background, border, feedbackUnderlay, @@ -140,7 +113,6 @@ internal class CompositeBackgroundDrawable( context, originalBackground, outerShadows, - cssBackground, background, border, feedbackUnderlay, @@ -156,7 +128,6 @@ internal class CompositeBackgroundDrawable( context, originalBackground, outerShadows, - cssBackground, background, border, feedbackUnderlay, @@ -172,7 +143,6 @@ internal class CompositeBackgroundDrawable( context, originalBackground, outerShadows, - cssBackground, background, border, newUnderlay, @@ -230,7 +200,6 @@ internal class CompositeBackgroundDrawable( private fun createLayersArray( originalBackground: Drawable?, outerShadows: List, - cssBackground: CSSBackgroundDrawable?, background: BackgroundDrawable?, border: BorderDrawable?, feedbackUnderlay: Drawable?, @@ -240,7 +209,6 @@ internal class CompositeBackgroundDrawable( val layers = mutableListOf() originalBackground?.let { layers.add(it) } layers.addAll(outerShadows.asReversed()) - cssBackground?.let { layers.add(it) } background?.let { layers.add(it) } border?.let { layers.add(it) } feedbackUnderlay?.let { layers.add(it) } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index d34c49a3ce88..0f09e8d9df43 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<16b12024bb363358ef09b9a42cb2fc97>> + * @generated SignedSource<<1b12c7b671d9093f5fe503dcd041e890>> */ /** @@ -231,12 +231,6 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } - bool enableNewBackgroundAndBorderDrawables() override { - static const auto method = - getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableNewBackgroundAndBorderDrawables"); - return method(javaProvider_); - } - bool enablePreparedTextLayout() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enablePreparedTextLayout"); @@ -617,11 +611,6 @@ bool JReactNativeFeatureFlagsCxxInterop::enableNetworkEventReporting( return ReactNativeFeatureFlags::enableNetworkEventReporting(); } -bool JReactNativeFeatureFlagsCxxInterop::enableNewBackgroundAndBorderDrawables( - facebook::jni::alias_ref /*unused*/) { - return ReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables(); -} - bool JReactNativeFeatureFlagsCxxInterop::enablePreparedTextLayout( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enablePreparedTextLayout(); @@ -929,9 +918,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableNetworkEventReporting", JReactNativeFeatureFlagsCxxInterop::enableNetworkEventReporting), - makeNativeMethod( - "enableNewBackgroundAndBorderDrawables", - JReactNativeFeatureFlagsCxxInterop::enableNewBackgroundAndBorderDrawables), makeNativeMethod( "enablePreparedTextLayout", JReactNativeFeatureFlagsCxxInterop::enablePreparedTextLayout), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index d3961e399de0..5b14c69f8c18 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<54118ccd475a8bf1d7db83304b1f17d0>> + * @generated SignedSource<> */ /** @@ -126,9 +126,6 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableNetworkEventReporting( facebook::jni::alias_ref); - static bool enableNewBackgroundAndBorderDrawables( - facebook::jni::alias_ref); - static bool enablePreparedTextLayout( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index a57a919cf577..8edc164e0094 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<12a06ea04fc09c34f1fbdcbdf6046d81>> + * @generated SignedSource<<1f30ac4b3a008f1a8cc8b08d5c3b1d36>> */ /** @@ -154,10 +154,6 @@ bool ReactNativeFeatureFlags::enableNetworkEventReporting() { return getAccessor().enableNetworkEventReporting(); } -bool ReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables() { - return getAccessor().enableNewBackgroundAndBorderDrawables(); -} - bool ReactNativeFeatureFlags::enablePreparedTextLayout() { return getAccessor().enablePreparedTextLayout(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 1a7258293322..354887847d9c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<550c6084049b176c9b3c220962c04ed7>> */ /** @@ -199,11 +199,6 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableNetworkEventReporting(); - /** - * Use BackgroundDrawable and BorderDrawable instead of CSSBackgroundDrawable - */ - RN_EXPORT static bool enableNewBackgroundAndBorderDrawables(); - /** * Enables caching text layout artifacts for later reuse */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 77c7804d6679..cbd87c10463c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3c5588a851e6cdefaba22236c5ebb828>> + * @generated SignedSource<<3819144ebf9282bea8386e9433fd2efd>> */ /** @@ -605,24 +605,6 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { return flagValue.value(); } -bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() { - auto flagValue = enableNewBackgroundAndBorderDrawables_.load(); - - if (!flagValue.has_value()) { - // This block is not exclusive but it is not necessary. - // If multiple threads try to initialize the feature flag, we would only - // be accessing the provider multiple times but the end state of this - // instance and the returned flag value would be the same. - - markFlagAsAccessed(32, "enableNewBackgroundAndBorderDrawables"); - - flagValue = currentProvider_->enableNewBackgroundAndBorderDrawables(); - enableNewBackgroundAndBorderDrawables_ = flagValue; - } - - return flagValue.value(); -} - bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { auto flagValue = enablePreparedTextLayout_.load(); @@ -632,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(33, "enablePreparedTextLayout"); + markFlagAsAccessed(32, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -650,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(33, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -668,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "enableResourceTimingAPI"); + markFlagAsAccessed(34, "enableResourceTimingAPI"); flagValue = currentProvider_->enableResourceTimingAPI(); enableResourceTimingAPI_ = flagValue; @@ -686,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableViewCulling"); + markFlagAsAccessed(35, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -704,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "enableViewRecycling"); + markFlagAsAccessed(36, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -722,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(37, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -740,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableViewRecyclingForText"); + markFlagAsAccessed(38, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -758,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableViewRecyclingForView"); + markFlagAsAccessed(39, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -776,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableVirtualViewDebugFeatures"); + markFlagAsAccessed(40, "enableVirtualViewDebugFeatures"); flagValue = currentProvider_->enableVirtualViewDebugFeatures(); enableVirtualViewDebugFeatures_ = flagValue; @@ -794,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewRenderState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enableVirtualViewRenderState"); + markFlagAsAccessed(41, "enableVirtualViewRenderState"); flagValue = currentProvider_->enableVirtualViewRenderState(); enableVirtualViewRenderState_ = flagValue; @@ -812,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewWindowFocusDetection() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "enableVirtualViewWindowFocusDetection"); + markFlagAsAccessed(42, "enableVirtualViewWindowFocusDetection"); flagValue = currentProvider_->enableVirtualViewWindowFocusDetection(); enableVirtualViewWindowFocusDetection_ = flagValue; @@ -830,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(43, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -848,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "fuseboxEnabledRelease"); + markFlagAsAccessed(44, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -866,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(45, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -884,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(46, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -902,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "perfMonitorV2Enabled"); + markFlagAsAccessed(47, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -920,7 +902,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "preparedTextCacheSize"); + markFlagAsAccessed(48, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -938,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(49, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -956,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "releaseImageDataWhenConsumed"); + markFlagAsAccessed(50, "releaseImageDataWhenConsumed"); flagValue = currentProvider_->releaseImageDataWhenConsumed(); releaseImageDataWhenConsumed_ = flagValue; @@ -974,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(51, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -992,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(52, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1010,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(53, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1028,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(54, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1046,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(55, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1064,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(56, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1082,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useFabricInterop"); + markFlagAsAccessed(57, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1100,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(58, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1118,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(59, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1136,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(60, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1154,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(61, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1172,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "useRawPropsJsiValue"); + markFlagAsAccessed(62, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1190,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "useShadowNodeStateOnClone"); + markFlagAsAccessed(63, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1208,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "useTurboModuleInterop"); + markFlagAsAccessed(64, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1226,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "useTurboModules"); + markFlagAsAccessed(65, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1244,7 +1226,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "virtualViewHysteresisRatio"); + markFlagAsAccessed(66, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1262,7 +1244,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "virtualViewPrerenderRatio"); + markFlagAsAccessed(67, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 00cfe5ac235a..c123b98694f7 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<2a7c278ab1dd4e01aaaf4f492aaba38d>> */ /** @@ -64,7 +64,6 @@ class ReactNativeFeatureFlagsAccessor { bool enableModuleArgumentNSNullConversionIOS(); bool enableNativeCSSParsing(); bool enableNetworkEventReporting(); - bool enableNewBackgroundAndBorderDrawables(); bool enablePreparedTextLayout(); bool enablePropsUpdateReconciliationAndroid(); bool enableResourceTimingAPI(); @@ -112,7 +111,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 69> accessedFeatureFlags_; + std::array, 68> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -146,7 +145,6 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableModuleArgumentNSNullConversionIOS_; std::atomic> enableNativeCSSParsing_; std::atomic> enableNetworkEventReporting_; - std::atomic> enableNewBackgroundAndBorderDrawables_; std::atomic> enablePreparedTextLayout_; std::atomic> enablePropsUpdateReconciliationAndroid_; std::atomic> enableResourceTimingAPI_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2342fe6526ba..3adc38d778a4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<04b27e0e277162810e72223801717105>> */ /** @@ -155,10 +155,6 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } - bool enableNewBackgroundAndBorderDrawables() override { - return true; - } - bool enablePreparedTextLayout() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 61cab1e2ef2b..91b22308512d 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -333,15 +333,6 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableNetworkEventReporting(); } - bool enableNewBackgroundAndBorderDrawables() override { - auto value = values_["enableNewBackgroundAndBorderDrawables"]; - if (!value.isNull()) { - return value.getBool(); - } - - return ReactNativeFeatureFlagsDefaults::enableNewBackgroundAndBorderDrawables(); - } - bool enablePreparedTextLayout() override { auto value = values_["enablePreparedTextLayout"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 5880a958bd8c..de917b8b8009 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -57,7 +57,6 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableModuleArgumentNSNullConversionIOS() = 0; virtual bool enableNativeCSSParsing() = 0; virtual bool enableNetworkEventReporting() = 0; - virtual bool enableNewBackgroundAndBorderDrawables() = 0; virtual bool enablePreparedTextLayout() = 0; virtual bool enablePropsUpdateReconciliationAndroid() = 0; virtual bool enableResourceTimingAPI() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index ba23b4f6acca..52aed1ca6781 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<5bdc8aaaa8866a586a8cb5321dce37d2>> */ /** @@ -204,11 +204,6 @@ bool NativeReactNativeFeatureFlags::enableNetworkEventReporting( return ReactNativeFeatureFlags::enableNetworkEventReporting(); } -bool NativeReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables( - jsi::Runtime& /*runtime*/) { - return ReactNativeFeatureFlags::enableNewBackgroundAndBorderDrawables(); -} - bool NativeReactNativeFeatureFlags::enablePreparedTextLayout( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enablePreparedTextLayout(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index cf3082dbe146..33d31435c50c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<320e69fa54228a352fad210e3a43b947>> + * @generated SignedSource<<16f5ac0ef234b1390b367ec6933237b5>> */ /** @@ -100,8 +100,6 @@ class NativeReactNativeFeatureFlags bool enableNetworkEventReporting(jsi::Runtime& runtime); - bool enableNewBackgroundAndBorderDrawables(jsi::Runtime& runtime); - bool enablePreparedTextLayout(jsi::Runtime& runtime); bool enablePropsUpdateReconciliationAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 9453c2539d74..309504f368ee 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -387,17 +387,6 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, - enableNewBackgroundAndBorderDrawables: { - defaultValue: true, - metadata: { - dateAdded: '2024-09-24', - description: - 'Use BackgroundDrawable and BorderDrawable instead of CSSBackgroundDrawable', - expectedReleaseValue: true, - purpose: 'experimentation', - }, - ossReleaseStage: 'none', - }, enablePreparedTextLayout: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 929699d8c8c4..e503544cfd2c 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<85a053d6a79240a7b4e181d3054ecfd3>> + * @generated SignedSource<<6d3f1638ab167e175d421c0c08f2320b>> * @flow strict * @noformat */ @@ -81,7 +81,6 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableModuleArgumentNSNullConversionIOS: Getter, enableNativeCSSParsing: Getter, enableNetworkEventReporting: Getter, - enableNewBackgroundAndBorderDrawables: Getter, enablePreparedTextLayout: Getter, enablePropsUpdateReconciliationAndroid: Getter, enableResourceTimingAPI: Getter, @@ -322,10 +321,6 @@ export const enableNativeCSSParsing: Getter = createNativeFlagGetter('e * Enable network event reporting hooks in each native platform through `NetworkReporter`. This flag should be combined with `enableResourceTimingAPI` and `fuseboxNetworkInspectionEnabled` to enable end-to-end reporting behaviour via the Web Performance API and CDP debugging respectively. */ export const enableNetworkEventReporting: Getter = createNativeFlagGetter('enableNetworkEventReporting', false); -/** - * Use BackgroundDrawable and BorderDrawable instead of CSSBackgroundDrawable - */ -export const enableNewBackgroundAndBorderDrawables: Getter = createNativeFlagGetter('enableNewBackgroundAndBorderDrawables', true); /** * Enables caching text layout artifacts for later reuse */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 2ed27daeb817..f6f15faf6041 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<3185662f33b62ad8571fd5ec61d39063>> * @flow strict * @noformat */ @@ -57,7 +57,6 @@ export interface Spec extends TurboModule { +enableModuleArgumentNSNullConversionIOS?: () => boolean; +enableNativeCSSParsing?: () => boolean; +enableNetworkEventReporting?: () => boolean; - +enableNewBackgroundAndBorderDrawables?: () => boolean; +enablePreparedTextLayout?: () => boolean; +enablePropsUpdateReconciliationAndroid?: () => boolean; +enableResourceTimingAPI?: () => boolean; From 61deab7f949461e737f67337a527b9022169b806 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 2 Sep 2025 10:44:59 -0700 Subject: [PATCH 0016/1110] Add feature flag to trigger Android image prefetch request on the UI thread (#53554) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53554 Changelog: [Internal] Reviewed By: lenaic Differential Revision: D81468680 fbshipit-source-id: 9da40feaf90756645d2aed5c051dc137d7a90534 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 ++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 108 ++++++++++-------- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 11 ++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 20 files changed, 181 insertions(+), 64 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 79573182f0ca..3887895e3484 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6a0977519362184efd85bd1807b2ad25>> + * @generated SignedSource<> */ /** @@ -174,6 +174,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableImagePrefetchingAndroid(): Boolean = accessor.enableImagePrefetchingAndroid() + /** + * When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread + */ + @JvmStatic + public fun enableImagePrefetchingOnUiThreadAndroid(): Boolean = accessor.enableImagePrefetchingOnUiThreadAndroid() + /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 9e18b80a559e..c62dae26aea3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<7dbb21bddd9c2ae447c83923a25372e4>> */ /** @@ -44,6 +44,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null private var enableIOSViewClipToPaddingBoxCache: Boolean? = null private var enableImagePrefetchingAndroidCache: Boolean? = null + private var enableImagePrefetchingOnUiThreadAndroidCache: Boolean? = null private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null private var enableLayoutAnimationsOnAndroidCache: Boolean? = null @@ -305,6 +306,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean { + var cached = enableImagePrefetchingOnUiThreadAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableImagePrefetchingOnUiThreadAndroid() + enableImagePrefetchingOnUiThreadAndroidCache = cached + } + return cached + } + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean { var cached = enableImmediateUpdateModeForContentOffsetChangesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 5bff149788ed..c197fee37e5b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7b0b5efc545427de6f104cc36f42d0a9>> + * @generated SignedSource<<4f93b4eb8f3cb427d3b926ffa18f9f9b>> */ /** @@ -76,6 +76,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableImagePrefetchingAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableImagePrefetchingOnUiThreadAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean @DoNotStrip @JvmStatic public external fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 3b82d5df4de5..eb4455840e88 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7de4be16480ca4df9f2a6655a469c6c7>> + * @generated SignedSource<<731176315c5589990865a0995f8b7e20>> */ /** @@ -71,6 +71,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableImagePrefetchingAndroid(): Boolean = false + override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean = false + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 62648627588a..928411609dc3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<72146cdfb6cc16ead73858c970e4221a>> + * @generated SignedSource<<7af6546cb6e02afcfca90635b3cbe603>> */ /** @@ -48,6 +48,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null private var enableIOSViewClipToPaddingBoxCache: Boolean? = null private var enableImagePrefetchingAndroidCache: Boolean? = null + private var enableImagePrefetchingOnUiThreadAndroidCache: Boolean? = null private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null private var enableLayoutAnimationsOnAndroidCache: Boolean? = null @@ -333,6 +334,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean { + var cached = enableImagePrefetchingOnUiThreadAndroidCache + if (cached == null) { + cached = currentProvider.enableImagePrefetchingOnUiThreadAndroid() + accessedFeatureFlags.add("enableImagePrefetchingOnUiThreadAndroid") + enableImagePrefetchingOnUiThreadAndroidCache = cached + } + return cached + } + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean { var cached = enableImmediateUpdateModeForContentOffsetChangesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index bf081d50419f..ce33940cffcf 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<47efc0544de6bdeec45ee4a7f1aa31fa>> + * @generated SignedSource<<9a982d4167179619629627529916259b>> */ /** @@ -71,6 +71,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableImagePrefetchingAndroid(): Boolean + @DoNotStrip public fun enableImagePrefetchingOnUiThreadAndroid(): Boolean + @DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean @DoNotStrip public fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 0f09e8d9df43..566dcc1a638b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1b12c7b671d9093f5fe503dcd041e890>> + * @generated SignedSource<<6e451f1b402ee9f80e86d0ee6b808f05>> */ /** @@ -183,6 +183,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableImagePrefetchingOnUiThreadAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableImagePrefetchingOnUiThreadAndroid"); + return method(javaProvider_); + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableImmediateUpdateModeForContentOffsetChanges"); @@ -571,6 +577,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingAndroid( return ReactNativeFeatureFlags::enableImagePrefetchingAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingOnUiThreadAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(); @@ -894,6 +905,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableImagePrefetchingAndroid", JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingAndroid), + makeNativeMethod( + "enableImagePrefetchingOnUiThreadAndroid", + JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingOnUiThreadAndroid), makeNativeMethod( "enableImmediateUpdateModeForContentOffsetChanges", JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 5b14c69f8c18..534d8e55737e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<81d70a5d005d308fdc3df3d0e7ec69b9>> */ /** @@ -102,6 +102,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableImagePrefetchingAndroid( facebook::jni::alias_ref); + static bool enableImagePrefetchingOnUiThreadAndroid( + facebook::jni::alias_ref); + static bool enableImmediateUpdateModeForContentOffsetChanges( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 8edc164e0094..ee2319de5b7e 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1f30ac4b3a008f1a8cc8b08d5c3b1d36>> + * @generated SignedSource<> */ /** @@ -122,6 +122,10 @@ bool ReactNativeFeatureFlags::enableImagePrefetchingAndroid() { return getAccessor().enableImagePrefetchingAndroid(); } +bool ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid() { + return getAccessor().enableImagePrefetchingOnUiThreadAndroid(); +} + bool ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges() { return getAccessor().enableImmediateUpdateModeForContentOffsetChanges(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 354887847d9c..8cfe42f56c61 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<550c6084049b176c9b3c220962c04ed7>> + * @generated SignedSource<<83c1c5b181d47f4a54cb097f8d31c48a>> */ /** @@ -159,6 +159,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableImagePrefetchingAndroid(); + /** + * When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread + */ + RN_EXPORT static bool enableImagePrefetchingOnUiThreadAndroid(); + /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index cbd87c10463c..23e318880729 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3819144ebf9282bea8386e9433fd2efd>> + * @generated SignedSource<<5a84827d553809907a015b01cc5cff66>> */ /** @@ -461,6 +461,24 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingAndroid() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingOnUiThreadAndroid() { + auto flagValue = enableImagePrefetchingOnUiThreadAndroid_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(24, "enableImagePrefetchingOnUiThreadAndroid"); + + flagValue = currentProvider_->enableImagePrefetchingOnUiThreadAndroid(); + enableImagePrefetchingOnUiThreadAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetChanges() { auto flagValue = enableImmediateUpdateModeForContentOffsetChanges_.load(); @@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetC // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(24, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(25, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimiz // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(26, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(26, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(27, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(27, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(28, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueCoordinatorOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(28, "enableMainQueueCoordinatorOnIOS"); + markFlagAsAccessed(29, "enableMainQueueCoordinatorOnIOS"); flagValue = currentProvider_->enableMainQueueCoordinatorOnIOS(); enableMainQueueCoordinatorOnIOS_ = flagValue; @@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(29, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(30, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "enableNativeCSSParsing"); + markFlagAsAccessed(31, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(31, "enableNetworkEventReporting"); + markFlagAsAccessed(32, "enableNetworkEventReporting"); flagValue = currentProvider_->enableNetworkEventReporting(); enableNetworkEventReporting_ = flagValue; @@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(32, "enablePreparedTextLayout"); + markFlagAsAccessed(33, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(33, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(34, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableResourceTimingAPI"); + markFlagAsAccessed(35, "enableResourceTimingAPI"); flagValue = currentProvider_->enableResourceTimingAPI(); enableResourceTimingAPI_ = flagValue; @@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "enableViewCulling"); + markFlagAsAccessed(36, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableViewRecycling"); + markFlagAsAccessed(37, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(38, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableViewRecyclingForText"); + markFlagAsAccessed(39, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableViewRecyclingForView"); + markFlagAsAccessed(40, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableVirtualViewDebugFeatures"); + markFlagAsAccessed(41, "enableVirtualViewDebugFeatures"); flagValue = currentProvider_->enableVirtualViewDebugFeatures(); enableVirtualViewDebugFeatures_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewRenderState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableVirtualViewRenderState"); + markFlagAsAccessed(42, "enableVirtualViewRenderState"); flagValue = currentProvider_->enableVirtualViewRenderState(); enableVirtualViewRenderState_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewWindowFocusDetection() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enableVirtualViewWindowFocusDetection"); + markFlagAsAccessed(43, "enableVirtualViewWindowFocusDetection"); flagValue = currentProvider_->enableVirtualViewWindowFocusDetection(); enableVirtualViewWindowFocusDetection_ = flagValue; @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(44, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "fuseboxEnabledRelease"); + markFlagAsAccessed(45, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(46, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(47, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "perfMonitorV2Enabled"); + markFlagAsAccessed(48, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -902,7 +920,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "preparedTextCacheSize"); + markFlagAsAccessed(49, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(50, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "releaseImageDataWhenConsumed"); + markFlagAsAccessed(51, "releaseImageDataWhenConsumed"); flagValue = currentProvider_->releaseImageDataWhenConsumed(); releaseImageDataWhenConsumed_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(52, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(53, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(54, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(55, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(56, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(57, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useFabricInterop"); + markFlagAsAccessed(58, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(59, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(60, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(61, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(62, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "useRawPropsJsiValue"); + markFlagAsAccessed(63, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "useShadowNodeStateOnClone"); + markFlagAsAccessed(64, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "useTurboModuleInterop"); + markFlagAsAccessed(65, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "useTurboModules"); + markFlagAsAccessed(66, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1226,7 +1244,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "virtualViewHysteresisRatio"); + markFlagAsAccessed(67, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1244,7 +1262,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "virtualViewPrerenderRatio"); + markFlagAsAccessed(68, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index c123b98694f7..09227a0307ed 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2a7c278ab1dd4e01aaaf4f492aaba38d>> + * @generated SignedSource<<3e2e6e95c530a18cfd248908e99d6878>> */ /** @@ -56,6 +56,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableIOSTextBaselineOffsetPerLine(); bool enableIOSViewClipToPaddingBox(); bool enableImagePrefetchingAndroid(); + bool enableImagePrefetchingOnUiThreadAndroid(); bool enableImmediateUpdateModeForContentOffsetChanges(); bool enableInteropViewManagerClassLookUpOptimizationIOS(); bool enableLayoutAnimationsOnAndroid(); @@ -111,7 +112,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 68> accessedFeatureFlags_; + std::array, 69> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -137,6 +138,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableIOSTextBaselineOffsetPerLine_; std::atomic> enableIOSViewClipToPaddingBox_; std::atomic> enableImagePrefetchingAndroid_; + std::atomic> enableImagePrefetchingOnUiThreadAndroid_; std::atomic> enableImmediateUpdateModeForContentOffsetChanges_; std::atomic> enableInteropViewManagerClassLookUpOptimizationIOS_; std::atomic> enableLayoutAnimationsOnAndroid_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 3adc38d778a4..7374f24f010b 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<04b27e0e277162810e72223801717105>> + * @generated SignedSource<<1285e1c404d7a9554a5285ac3dc63cd1>> */ /** @@ -123,6 +123,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableImagePrefetchingOnUiThreadAndroid() override { + return false; + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 91b22308512d..ee6e163db8a1 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -261,6 +261,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingAndroid(); } + bool enableImagePrefetchingOnUiThreadAndroid() override { + auto value = values_["enableImagePrefetchingOnUiThreadAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingOnUiThreadAndroid(); + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { auto value = values_["enableImmediateUpdateModeForContentOffsetChanges"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index de917b8b8009..478a9ce0b717 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<31a4308a13ed1445409f3e5edfd0d388>> */ /** @@ -49,6 +49,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableIOSTextBaselineOffsetPerLine() = 0; virtual bool enableIOSViewClipToPaddingBox() = 0; virtual bool enableImagePrefetchingAndroid() = 0; + virtual bool enableImagePrefetchingOnUiThreadAndroid() = 0; virtual bool enableImmediateUpdateModeForContentOffsetChanges() = 0; virtual bool enableInteropViewManagerClassLookUpOptimizationIOS() = 0; virtual bool enableLayoutAnimationsOnAndroid() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 52aed1ca6781..de00df10bb76 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5bdc8aaaa8866a586a8cb5321dce37d2>> + * @generated SignedSource<<40877a198ee2fbcbed13d85abc6f793d>> */ /** @@ -164,6 +164,11 @@ bool NativeReactNativeFeatureFlags::enableImagePrefetchingAndroid( return ReactNativeFeatureFlags::enableImagePrefetchingAndroid(); } +bool NativeReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid(); +} + bool NativeReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 33d31435c50c..02644d780bbd 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<16f5ac0ef234b1390b367ec6933237b5>> + * @generated SignedSource<<77f248a7c43e9f11f6e727319a72e1a0>> */ /** @@ -84,6 +84,8 @@ class NativeReactNativeFeatureFlags bool enableImagePrefetchingAndroid(jsi::Runtime& runtime); + bool enableImagePrefetchingOnUiThreadAndroid(jsi::Runtime& runtime); + bool enableImmediateUpdateModeForContentOffsetChanges(jsi::Runtime& runtime); bool enableInteropViewManagerClassLookUpOptimizationIOS(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 309504f368ee..33a52ffe0af2 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -303,6 +303,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableImagePrefetchingOnUiThreadAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2025-09-02', + description: + 'When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableImmediateUpdateModeForContentOffsetChanges: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index e503544cfd2c..c6dc1ec855f4 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6d3f1638ab167e175d421c0c08f2320b>> + * @generated SignedSource<<8ac45d7dfef86becd1f0a7ec7ba8e999>> * @flow strict * @noformat */ @@ -73,6 +73,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableIOSTextBaselineOffsetPerLine: Getter, enableIOSViewClipToPaddingBox: Getter, enableImagePrefetchingAndroid: Getter, + enableImagePrefetchingOnUiThreadAndroid: Getter, enableImmediateUpdateModeForContentOffsetChanges: Getter, enableInteropViewManagerClassLookUpOptimizationIOS: Getter, enableLayoutAnimationsOnAndroid: Getter, @@ -289,6 +290,10 @@ export const enableIOSViewClipToPaddingBox: Getter = createNativeFlagGe * When enabled, Android will build and initiate image prefetch requests on ImageShadowNode::layout */ export const enableImagePrefetchingAndroid: Getter = createNativeFlagGetter('enableImagePrefetchingAndroid', false); +/** + * When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread + */ +export const enableImagePrefetchingOnUiThreadAndroid: Getter = createNativeFlagGetter('enableImagePrefetchingOnUiThreadAndroid', false); /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index f6f15faf6041..77024df92d82 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3185662f33b62ad8571fd5ec61d39063>> + * @generated SignedSource<<6d699b7dc91fce5814db79d7f19ba511>> * @flow strict * @noformat */ @@ -49,6 +49,7 @@ export interface Spec extends TurboModule { +enableIOSTextBaselineOffsetPerLine?: () => boolean; +enableIOSViewClipToPaddingBox?: () => boolean; +enableImagePrefetchingAndroid?: () => boolean; + +enableImagePrefetchingOnUiThreadAndroid?: () => boolean; +enableImmediateUpdateModeForContentOffsetChanges?: () => boolean; +enableInteropViewManagerClassLookUpOptimizationIOS?: () => boolean; +enableLayoutAnimationsOnAndroid?: () => boolean; From 05c4321b194c3d0e146b6085bcaccc75acd3fd67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Tue, 2 Sep 2025 11:19:42 -0700 Subject: [PATCH 0017/1110] fix: fallback alert controller to UIScreen size (#53500) Summary: This PR falls back to UIScreen when windowScene is not available. CleanShot 2025-08-28 at 14 30 59@2x ## Changelog: [IOS] [FIXED] - Simplify RCTAlertController, don't create additional UIWindow Pull Request resolved: https://github.com/facebook/react-native/pull/53500 Test Plan: Open the alert multiple times to check if everything works as expected. Rollback Plan: Reviewed By: javache Differential Revision: D81410450 Pulled By: cipolleschi fbshipit-source-id: c27ea98d9e811c2f259f0ff3c6689482d116c418 --- .../react-native/React/CoreModules/RCTAlertController.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/CoreModules/RCTAlertController.mm b/packages/react-native/React/CoreModules/RCTAlertController.mm index b47ce0adefa0..3131ff4db592 100644 --- a/packages/react-native/React/CoreModules/RCTAlertController.mm +++ b/packages/react-native/React/CoreModules/RCTAlertController.mm @@ -20,7 +20,12 @@ @implementation RCTAlertController - (UIWindow *)alertWindow { if (_alertWindow == nil) { - _alertWindow = [[UIWindow alloc] initWithWindowScene:RCTKeyWindow().windowScene]; + UIWindowScene *scene = RCTKeyWindow().windowScene; + if (scene != nil) { + _alertWindow = [[UIWindow alloc] initWithWindowScene:scene]; + } else { + _alertWindow = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + } if (_alertWindow) { _alertWindow.rootViewController = [UIViewController new]; From c1320eb2e144c4916d1f8e35d3736cce3672f567 Mon Sep 17 00:00:00 2001 From: generatedunixname537391475639613 Date: Tue, 2 Sep 2025 14:58:53 -0700 Subject: [PATCH 0018/1110] xplat/js/react-native-github/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt (#53559) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53559 Reviewed By: cortinico Differential Revision: D81476261 fbshipit-source-id: f3f38664a7dc63a11b027ab2b6a5a65ca374ebaa --- .../com/facebook/react/TaskConfiguration.kt | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt index db6d3f3b0ff4..9984aa67ae8a 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -65,25 +65,26 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio if (!isDebuggableVariant) { val entryFileEnvVariable = System.getenv("ENTRY_FILE") val bundleTask = - tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) { - it.root.set(config.root) - it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs) - it.cliFile.set(cliFile) - it.bundleCommand.set(config.bundleCommand) - it.entryFile.set(detectedEntryFile(config, entryFileEnvVariable)) - it.extraPackagerArgs.set(config.extraPackagerArgs) - it.bundleConfig.set(config.bundleConfig) - it.bundleAssetName.set(config.bundleAssetName) - it.jsBundleDir.set(jsBundleDir) - it.resourcesDir.set(resourcesDir) - it.hermesEnabled.set(isHermesEnabledInThisVariant) - it.minifyEnabled.set(!isHermesEnabledInThisVariant) - it.devEnabled.set(false) - it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) - it.jsSourceMapsDir.set(jsSourceMapsDir) - it.hermesCommand.set(config.hermesCommand) - it.hermesFlags.set(config.hermesFlags) - it.reactNativeDir.set(config.reactNativeDir) + tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) { task + -> + task.root.set(config.root) + task.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs) + task.cliFile.set(cliFile) + task.bundleCommand.set(config.bundleCommand) + task.entryFile.set(detectedEntryFile(config, entryFileEnvVariable)) + task.extraPackagerArgs.set(config.extraPackagerArgs) + task.bundleConfig.set(config.bundleConfig) + task.bundleAssetName.set(config.bundleAssetName) + task.jsBundleDir.set(jsBundleDir) + task.resourcesDir.set(resourcesDir) + task.hermesEnabled.set(isHermesEnabledInThisVariant) + task.minifyEnabled.set(!isHermesEnabledInThisVariant) + task.devEnabled.set(false) + task.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir) + task.jsSourceMapsDir.set(jsSourceMapsDir) + task.hermesCommand.set(config.hermesCommand) + task.hermesFlags.set(config.hermesFlags) + task.reactNativeDir.set(config.reactNativeDir) } variant.sources.res?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::resourcesDir) variant.sources.assets?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::jsBundleDir) From 1604232e8dfe5a12337645af4dc00d8c79080fec Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Tue, 2 Sep 2025 16:28:02 -0700 Subject: [PATCH 0019/1110] VirtualView: Create Experimental Feature Flag (#53533) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53533 Creates a new `enableVirtualViewExperimental` feature flag that determines whether `VirtualView` uses the old or new implementation. Changelog: [Internal] Reviewed By: lunaleaps Differential Revision: D81340963 fbshipit-source-id: f550fe4e4573e080eb8668077d0ad3ca53cd4d33 --- .../ReactNativeFeatureFlags.config.js | 10 ++++++ .../components/virtualview/VirtualView.js | 34 +++++++------------ .../featureflags/ReactNativeFeatureFlags.js | 8 ++++- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 33a52ffe0af2..5b893d3daba7 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -839,6 +839,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'stable', }, + enableVirtualViewExperimental: { + defaultValue: false, + metadata: { + dateAdded: '2025-08-29', + description: 'Enables the experimental version of `VirtualView`.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, fixVirtualizeListCollapseWindowSize: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/components/virtualview/VirtualView.js b/packages/react-native/src/private/components/virtualview/VirtualView.js index d2330da72f07..c8652578eae6 100644 --- a/packages/react-native/src/private/components/virtualview/VirtualView.js +++ b/packages/react-native/src/private/components/virtualview/VirtualView.js @@ -16,7 +16,7 @@ import type {NativeModeChangeEvent} from './VirtualViewNativeComponent'; import StyleSheet from '../../../../Libraries/StyleSheet/StyleSheet'; import * as ReactNativeFeatureFlags from '../../featureflags/ReactNativeFeatureFlags'; import VirtualViewExperimentalNativeComponent from './VirtualViewExperimentalNativeComponent'; -import VirtualViewNativeComponent from './VirtualViewNativeComponent'; +import VirtualViewClassicNativeComponent from './VirtualViewNativeComponent'; import nullthrows from 'nullthrows'; import * as React from 'react'; // $FlowFixMe[missing-export] @@ -49,6 +49,11 @@ export type ModeChangeEvent = $ReadOnly<{ target: HostInstance, }>; +const VirtualViewNativeComponent: typeof VirtualViewClassicNativeComponent = + ReactNativeFeatureFlags.enableVirtualViewExperimental() + ? VirtualViewExperimentalNativeComponent + : VirtualViewClassicNativeComponent; + type VirtualViewComponent = component( children?: React.Node, nativeID?: string, @@ -63,16 +68,9 @@ const NotHidden = null; type State = HiddenHeight | typeof NotHidden; -function createVirtualView( - initialState: State, - experimental: boolean, -): VirtualViewComponent { +function createVirtualView(initialState: State): VirtualViewComponent { const initialHidden = initialState !== NotHidden; - const NativeComponent = experimental - ? VirtualViewExperimentalNativeComponent - : VirtualViewNativeComponent; - component VirtualView( children?: React.Node, nativeID?: string, @@ -124,7 +122,7 @@ function createVirtualView( }; return ( - isHidden ? null : children, } } - + ); } return VirtualView; } -export default createVirtualView(NotHidden, false) as VirtualViewComponent; - -export const VirtualViewExperimental = createVirtualView( - NotHidden, - true, -) as VirtualViewComponent; +export default createVirtualView(NotHidden) as VirtualViewComponent; -export function createHiddenVirtualView( - height: number, - experimental: boolean, -): VirtualViewComponent { - return createVirtualView(height as HiddenHeight, experimental); +export function createHiddenVirtualView(height: number): VirtualViewComponent { + return createVirtualView(height as HiddenHeight); } export const _logs: {states?: Array} = {}; diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index c6dc1ec855f4..69e673413d14 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8ac45d7dfef86becd1f0a7ec7ba8e999>> + * @generated SignedSource<<22523248acc3e378f3f81ef43406f106>> * @flow strict * @noformat */ @@ -34,6 +34,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{ deferFlatListFocusChangeRenderUpdate: Getter, disableMaintainVisibleContentPosition: Getter, enableAccessToHostTreeInFabric: Getter, + enableVirtualViewExperimental: Getter, fixVirtualizeListCollapseWindowSize: Getter, isLayoutAnimationEnabled: Getter, reduceDefaultPropsInImage: Getter, @@ -150,6 +151,11 @@ export const disableMaintainVisibleContentPosition: Getter = createJava */ export const enableAccessToHostTreeInFabric: Getter = createJavaScriptFlagGetter('enableAccessToHostTreeInFabric', true); +/** + * Enables the experimental version of `VirtualView`. + */ +export const enableVirtualViewExperimental: Getter = createJavaScriptFlagGetter('enableVirtualViewExperimental', false); + /** * Fixing an edge case where the current window size is not properly calculated with fast scrolling. Window size collapsed to 1 element even if windowSize more than the current amount of elements */ From d6ed32f8d64bd7e0e20b7ea8fa4ac6be76041ff0 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Tue, 2 Sep 2025 16:28:02 -0700 Subject: [PATCH 0020/1110] VirtualView: Configurable Hidden Layout (#53571) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53571 Changes `VirtualView` so that its layout when hidden can be configured by call sites. Previously, it was hardcoded to only retain the last known height. However, this logic only works for `VirtualView` children oriented in a column layout. This change enables the use of `VirtualView` in more flexible abstractions that require different hidden styles (e.g. row or grid orientations). Also, this changes the default behavior to set `minWidth` and `minHeight`, so that the default behavior is more general and more likely to work in a reasonable manner in more use cases. NOTE: Ideally, we would be able to default to using `flexBasis` instead. However, the `hiddenStyle` function receives a `Rect` and does not know whether the parent's flex direction is row or column to influence whether to use `targetRect.width` or `targetRect.height`. This is an opportunity for future improvement. Changelog: [Internal] Reviewed By: lunaleaps Differential Revision: D81344126 fbshipit-source-id: 33d9e81601b671059f97b4590816243cbd24734a --- .../components/virtualview/VirtualView.js | 23 +++++++++++------- .../__tests__/VirtualView-itest.js | 24 +++++++++---------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/react-native/src/private/components/virtualview/VirtualView.js b/packages/react-native/src/private/components/virtualview/VirtualView.js index c8652578eae6..51183386877c 100644 --- a/packages/react-native/src/private/components/virtualview/VirtualView.js +++ b/packages/react-native/src/private/components/virtualview/VirtualView.js @@ -56,6 +56,7 @@ const VirtualViewNativeComponent: typeof VirtualViewClassicNativeComponent = type VirtualViewComponent = component( children?: React.Node, + hiddenStyle?: (targetRect: Rect) => ViewStyleProp, nativeID?: string, ref?: ?React.RefSetter>, style?: ?ViewStyleProp, @@ -63,16 +64,21 @@ type VirtualViewComponent = component( removeClippedSubviews?: boolean, ); -type HiddenHeight = number; const NotHidden = null; +type HiddenStyle = Exclude; -type State = HiddenHeight | typeof NotHidden; +type State = HiddenStyle | typeof NotHidden; + +function defaultHiddenStyle(targetRect: Rect): ViewStyleProp { + return {minHeight: targetRect.height, minWidth: targetRect.width}; +} function createVirtualView(initialState: State): VirtualViewComponent { const initialHidden = initialState !== NotHidden; component VirtualView( children?: React.Node, + hiddenStyle: (targetRect: Rect) => ViewStyleProp = defaultHiddenStyle, nativeID?: string, ref?: ?React.RefSetter>, style?: ?ViewStyleProp, @@ -112,9 +118,8 @@ function createVirtualView(initialState: State): VirtualViewComponent { }); } VirtualViewMode.Hidden => { - const {height} = event.nativeEvent.targetRect; startTransition(() => { - setState(height as HiddenHeight); + setState(hiddenStyle(event.nativeEvent.targetRect) ?? {}); emitModeChange?.(); }); } @@ -134,9 +139,7 @@ function createVirtualView(initialState: State): VirtualViewComponent { } style={ isHidden - ? StyleSheet.compose(style, { - height: Math.abs(nullthrows(state) as HiddenHeight), - }) + ? StyleSheet.compose(style, nullthrows(state) as HiddenStyle) : style } onModeChange={handleModeChange}> @@ -159,8 +162,10 @@ function createVirtualView(initialState: State): VirtualViewComponent { export default createVirtualView(NotHidden) as VirtualViewComponent; -export function createHiddenVirtualView(height: number): VirtualViewComponent { - return createVirtualView(height as HiddenHeight); +export function createHiddenVirtualView( + style: ViewStyleProp, +): VirtualViewComponent { + return createVirtualView((style ?? {}) as HiddenStyle); } export const _logs: {states?: Array} = {}; diff --git a/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js b/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js index a7b5516f77b6..9a861bc3c896 100644 --- a/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js +++ b/packages/react-native/src/private/components/virtualview/__tests__/VirtualView-itest.js @@ -119,19 +119,19 @@ describe('mode changes', () => { }); describe('styles', () => { - test('does not set height when visible', () => { + test('does not set styles when visible', () => { const root = Fantom.createRoot(); Fantom.runTask(() => { root.render(); }); - expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( - , - ); + expect( + root.getRenderedOutput({props: ['minHeight', 'minWidth']}).toJSX(), + ).toEqual(); }); - test('does not set height when prerendered', () => { + test('does not set styles when prerendered', () => { const root = Fantom.createRoot(); const viewRef = createRef>(); @@ -141,12 +141,12 @@ describe('styles', () => { dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Prerender); - expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( - , - ); + expect( + root.getRenderedOutput({props: ['minHeight', 'minWidth']}).toJSX(), + ).toEqual(); }); - test('sets height when hidden', () => { + test('sets styles when hidden', () => { const root = Fantom.createRoot(); const viewRef = createRef>(); @@ -156,9 +156,9 @@ describe('styles', () => { dispatchModeChangeEvent(viewRef.current, VirtualViewMode.Hidden); - expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( - , - ); + expect( + root.getRenderedOutput({props: ['minHeight', 'minWidth']}).toJSX(), + ).toEqual(); }); }); From f33a1cd2605309b86201ac9669b95ca87530caec Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 2 Sep 2025 17:48:39 -0700 Subject: [PATCH 0021/1110] Android: Schedule image prefetching on tree commit (#53555) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53555 Changelog: [Internal] ## TLDR; We run the `ReactVitoImageManager.kt` on the Java Message Queue Thread (`mqt`) > Maybe running it on the `UiThread` (as Android view creation) solves the QE reegressions ## Issue > Your experiment [qe:enable_image_prefetching_android_v4] is significantly moving important metric(s) T235749297 > e.g negatively impact `sp_core` (Scroll Performance Core) https://fburl.com/deltoid3/ef2fd92e {F1981479985} ## Observation After adding Perfetto traces in D80717558 and building a `automation_fbandroid_art_arm64_for_perftest_profileable` build > I see 'larger amounts' of `experimental_prefetchResource` on the JavaScript Message Queue Thread {F1981479808} We do run this entire logic on the JavaScript Message Queue Thread https://www.internalfb.com/code/fbsource/[368503303835439955d87d79439a3d19d979cd40]/fbandroid/java/com/facebook/fresco/vito/rn/ReactVitoImageManager.kt?lines=253-260 However when normally `mounting` Shadow Nodes in RN Android we jump from the JavaScript Message Queue Thread to the Android UI Thread https://www.internalfb.com/code/fbsource/[68603b276cb9de1ae2ecb83ec4a789ae3db3b051]/xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp?lines=626%2C638 -> https://www.internalfb.com/code/fbsource/[68603b276cb9de1ae2ecb83ec4a789ae3db3b051]/xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp?lines=690%2C701%2C872-883 -> https://www.internalfb.com/code/fbsource/[68603b276cb9de1ae2ecb83ec4a789ae3db3b051]/xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java?lines=897%2C943 -> https://www.internalfb.com/code/fbsource/[68603b276cb9de1ae2ecb83ec4a789ae3db3b051]/xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java?lines=938-946 ## Idea Run `imagePrefetcher?.prefetchResource` also on the UI thread ## Resources ### :: GDoc - Image Prefetching for Android https://docs.google.com/document/d/1Yc5G5vuollx0I4tdXpE8Hgwn2DuIhJKwOTHak3g4Gu8/edit?fbclid=IwY2xjawLjgcBleHRuA2FlbQIxMQBicmlkETFra3N5WHg3OGV6UndYUmVTAR5KiXIDgrH2FW4HEBdezFBr2NqX4KPT6FzYQXD1sBRjEfq8d_x0JwQfeL_TXg_aem_ZqWb9dAJ59pHFfoHsrzwbw&pli=1&tab=t.0#heading=h.udv4z3lhwhf7 - React Field of View https://docs.google.com/document/d/1gHLF3oAv9JhKKcztM56iZZUPPWp0mBbjqDlosBkL1kE/edit?tab=t.0#heading=h.36p5puf8ufz7 ### :: Fb4A (Facebook for Android) The debug package name for fb4a `com.facebook.katana` is typically `com.facebook.wakizashi` ### :: Links - How to Perfetto profile fb4a https://www.internalfb.com/wiki/Luna_Wei/Building_a_fb4a_Profile_Build/ - Building Catalyst Profile Build https://www.internalfb.com/wiki/Luna_Wei/Building_Catalyst_Profile_Build/ - Marketplace QE Regression Guide https://www.internalfb.com/intern/staticdocs/marketplace/performance/my-experiment-is-regressing-perf/ - Install for Profileable build https://www.internalfb.com/wiki/Metatrace/Metatrace-install_for_Profileable_build/ - Metatrace https://www.internalfb.com/wiki/Metatrace/ ### Android Java Debug https://www.internalfb.com/wiki/Platfrom_Health_Learnings/Onboarding_Material_or_New-hired_Engineers/How_to_Debug_FB4A_0/ ``` arc focus clean --invalidate-caches-only arc focus --targets --open ``` in this case ``` arc focus --targets fb4a --open ``` It creates a `monoproject` now {F1981501090} Reviewed By: javache Differential Revision: D80950423 fbshipit-source-id: 5f1c4c096adab218a2d765d262901521bab2e6b3 --- .../react/fabric/FabricUIManager.java | 10 ++++-- .../mountitems/PrefetchResourcesMountItem.kt | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 225650584454..a459f15afa28 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -65,6 +65,7 @@ import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem; import com.facebook.react.fabric.mounting.mountitems.MountItem; import com.facebook.react.fabric.mounting.mountitems.MountItemFactory; +import com.facebook.react.fabric.mounting.mountitems.PrefetchResourcesMountItem; import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags; import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags; import com.facebook.react.internal.interop.InteropEventEmitter; @@ -985,8 +986,13 @@ public void runGuarded() { */ @UnstableReactNativeAPI public void experimental_prefetchResources(String componentName, ReadableMapBuffer params) { - mMountingManager.experimental_prefetchResources( - mReactApplicationContext, componentName, params); + if (ReactNativeFeatureFlags.enableImagePrefetchingOnUiThreadAndroid()) { + mMountItemDispatcher.addMountItem( + new PrefetchResourcesMountItem(mReactApplicationContext, componentName, params)); + } else { + mMountingManager.experimental_prefetchResources( + mReactApplicationContext, componentName, params); + } } void setBinding(FabricUIManagerBinding binding) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt new file mode 100644 index 000000000000..7b1b6e138264 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric.mounting.mountitems + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.common.annotations.FrameworkAPI +import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.common.mapbuffer.ReadableMapBuffer +import com.facebook.react.fabric.mounting.MountingManager + +internal class PrefetchResourcesMountItem( + private val reactApplicationContext: ReactApplicationContext, + private val componentName: String, + private val params: ReadableMapBuffer, +) : MountItem { + + @OptIn(UnstableReactNativeAPI::class, FrameworkAPI::class) + override fun execute(mountingManager: MountingManager) { + mountingManager.experimental_prefetchResources( + reactApplicationContext, + componentName, + params, + ) + } + + override fun getSurfaceId(): Int = -1 /* unused */ + + override fun toString(): String = "PrefetchResourcesMountItem" +} From 4365c1c9f7fc3c8fae4d11f4eb9762141e0e6c91 Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Tue, 2 Sep 2025 21:56:09 -0700 Subject: [PATCH 0022/1110] Cleanup codeless suppressions in xplat/js (#53573) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53573 Changelog: [Internal] Reviewed By: marcoww6 Differential Revision: D81552699 fbshipit-source-id: 71b104174a8ad7fbf360cdd87109ce034f49ec70 --- packages/community-cli-plugin/src/utils/version.js | 1 - .../Libraries/Lists/__flowtests__/SectionList-flowtest.js | 1 - packages/react-native/Libraries/Network/XMLHttpRequest.js | 2 -- .../StyleSheet/__flowtests__/StyleSheet-flowtest.js | 6 +----- .../DrawerLayoutAndroid/DrawerLayoutAndroidExample.js | 1 - .../PointerEventPointerOverOut.js | 1 - packages/rn-tester/scripts/utils.js | 1 - private/helloworld/lib/cli.js | 1 - 8 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/community-cli-plugin/src/utils/version.js b/packages/community-cli-plugin/src/utils/version.js index cb7f7442a734..27f455a8aaee 100644 --- a/packages/community-cli-plugin/src/utils/version.js +++ b/packages/community-cli-plugin/src/utils/version.js @@ -88,7 +88,6 @@ Diff: ${styleText(['dim', 'underline'], newVersion?.diffUrl ?? 'none')} } } -// $FlowFixMe function isDiffPurgeEntry(data: Partial): data is DiffPurge { return ( // $FlowFixMe[incompatible-type-guard] diff --git a/packages/react-native/Libraries/Lists/__flowtests__/SectionList-flowtest.js b/packages/react-native/Libraries/Lists/__flowtests__/SectionList-flowtest.js index 49345b0de800..6d8424f914bf 100644 --- a/packages/react-native/Libraries/Lists/__flowtests__/SectionList-flowtest.js +++ b/packages/react-native/Libraries/Lists/__flowtests__/SectionList-flowtest.js @@ -102,7 +102,6 @@ export function testBadSectionsShape(): React.MixedElement { ], }, ]; - // $FlowExpectedError - section missing `data` field return ; } diff --git a/packages/react-native/Libraries/Network/XMLHttpRequest.js b/packages/react-native/Libraries/Network/XMLHttpRequest.js index 96bf736437f2..73566311d3bd 100644 --- a/packages/react-native/Libraries/Network/XMLHttpRequest.js +++ b/packages/react-native/Libraries/Network/XMLHttpRequest.js @@ -649,8 +649,6 @@ class XMLHttpRequest extends EventTarget { this._url, this._headers, data, - /* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found - * when making Flow check .android.js files. */ nativeResponseType, incrementalEvents, this.timeout, diff --git a/packages/react-native/Libraries/StyleSheet/__flowtests__/StyleSheet-flowtest.js b/packages/react-native/Libraries/StyleSheet/__flowtests__/StyleSheet-flowtest.js index 533fc11a9dee..6ae30580f429 100644 --- a/packages/react-native/Libraries/StyleSheet/__flowtests__/StyleSheet-flowtest.js +++ b/packages/react-native/Libraries/StyleSheet/__flowtests__/StyleSheet-flowtest.js @@ -41,11 +41,7 @@ export function testBadCompose() { (StyleSheet.compose(textStyle, textStyle): ImageStyleProp); // $FlowExpectedError[incompatible-type] - Incompatible type. - (StyleSheet.compose( - // $FlowExpectedError - Incompatible type. - [textStyle], - null, - ): ImageStyleProp); + (StyleSheet.compose([textStyle], null): ImageStyleProp); // $FlowExpectedError[incompatible-type] - Incompatible type. (StyleSheet.compose( diff --git a/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js b/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js index 5e70b342aa4d..693ebd66044a 100644 --- a/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js +++ b/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js @@ -48,7 +48,6 @@ const Drawer = () => { return ( { }; }); - // $FlowFixMe return obs; } diff --git a/private/helloworld/lib/cli.js b/private/helloworld/lib/cli.js index e023bd0312df..331ab247413e 100644 --- a/private/helloworld/lib/cli.js +++ b/private/helloworld/lib/cli.js @@ -70,7 +70,6 @@ export function observe(result: ExecaPromiseMetaized): TaskResult<{}, string> { }; }); - // $FlowFixMe return obs; } From 9ef0d2134491f77c83d6826a46d2cab7be713646 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Wed, 3 Sep 2025 00:07:26 -0700 Subject: [PATCH 0023/1110] Pass surfaceId to imageRequest (#53572) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53572 Changelog: [Internal] Code refactoring to pass actual `surfaceid` to PrefetchResourcesMountItem Reviewed By: andrewdacenko Differential Revision: D81506929 fbshipit-source-id: 6c1cb91180cc23930986b258e2a8842560c0851a --- .../react/fabric/FabricUIManager.java | 11 +++++---- .../react/fabric/mounting/MountingManager.kt | 23 ------------------- .../mounting/SurfaceMountingManager.java | 22 ++++++++++++++++++ .../mountitems/PrefetchResourcesMountItem.kt | 17 +++++++------- .../facebook/react/uimanager/ViewManager.java | 4 +++- .../renderer/imagemanager/ImageFetcher.cpp | 16 ++++++++----- .../renderer/imagemanager/ImageFetcher.h | 4 +++- .../imagemanager/ImageRequestParams.h | 1 - .../react/renderer/imagemanager/conversions.h | 4 +--- 9 files changed, 55 insertions(+), 47 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index a459f15afa28..e08055776a3a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -985,13 +985,16 @@ public void runGuarded() { * by an ImageView. */ @UnstableReactNativeAPI - public void experimental_prefetchResources(String componentName, ReadableMapBuffer params) { + public void experimental_prefetchResources( + int surfaceId, String componentName, ReadableMapBuffer params) { if (ReactNativeFeatureFlags.enableImagePrefetchingOnUiThreadAndroid()) { mMountItemDispatcher.addMountItem( - new PrefetchResourcesMountItem(mReactApplicationContext, componentName, params)); + new PrefetchResourcesMountItem(surfaceId, componentName, params)); } else { - mMountingManager.experimental_prefetchResources( - mReactApplicationContext, componentName, params); + SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId); + if (surfaceMountingManager != null) { + surfaceMountingManager.experimental_prefetchResources(surfaceId, componentName, params); + } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt index 250a6db4f62d..ad8d51324332 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt @@ -19,8 +19,6 @@ import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.RetryableMountingLayerException import com.facebook.react.bridge.UiThreadUtil.assertOnUiThread import com.facebook.react.bridge.WritableMap -import com.facebook.react.common.annotations.UnstableReactNativeAPI -import com.facebook.react.common.mapbuffer.MapBuffer import com.facebook.react.fabric.events.EventEmitterWrapper import com.facebook.react.fabric.mounting.mountitems.MountItem import com.facebook.react.touch.JSResponderHandler @@ -325,27 +323,6 @@ internal class MountingManager( attachmentsPositions, ) - /** - * This prefetch method is experimental, do not use it for production code. it will most likely - * change or be removed in the future. - * - * @param reactContext - * @param componentName - * @param params prefetch request params defined in C++ - */ - @Suppress("FunctionName") - @AnyThread - @UnstableReactNativeAPI - fun experimental_prefetchResources( - reactContext: ReactContext?, - componentName: String?, - params: MapBuffer?, - ) { - viewManagerRegistry - .get(checkNotNull(componentName)) - .experimental_prefetchResources(reactContext, params) - } - fun enqueuePendingEvent( surfaceId: Int, reactTag: Int, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java index dee20e3f89b0..705ad0411a03 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java @@ -10,6 +10,7 @@ import static com.facebook.infer.annotation.ThreadConfined.ANY; import static com.facebook.infer.annotation.ThreadConfined.UI; +import android.annotation.SuppressLint; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -30,7 +31,9 @@ import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.common.annotations.UnstableReactNativeAPI; import com.facebook.react.common.build.ReactBuildConfig; +import com.facebook.react.common.mapbuffer.MapBuffer; import com.facebook.react.fabric.events.EventEmitterWrapper; import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor; import com.facebook.react.fabric.mounting.mountitems.MountItem; @@ -696,6 +699,25 @@ public void updateProps(int reactTag, ReadableMap props) { .updateProperties(view, viewState.mCurrentProps); } + /** + * This prefetch method is experimental, do not use it for production code. it will most likely + * change or be removed in the future. + * + * @param surfaceId surface ID + * @param componentName + * @param params prefetch request params defined in C++ + */ + @SuppressLint("FunctionName") + @AnyThread + @UnstableReactNativeAPI + public void experimental_prefetchResources( + int surfaceId, String componentName, MapBuffer params) { + mViewManagerRegistry + .get(componentName) + .experimental_prefetchResources( + surfaceId, Assertions.assertNotNull(mThemedReactContext), params); + } + @Deprecated public void receiveCommand(int reactTag, int commandId, ReadableArray commandArgs) { if (isStopped()) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt index 7b1b6e138264..2bceb57bfab5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PrefetchResourcesMountItem.kt @@ -7,28 +7,29 @@ package com.facebook.react.fabric.mounting.mountitems -import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.common.annotations.FrameworkAPI import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.mapbuffer.ReadableMapBuffer import com.facebook.react.fabric.mounting.MountingManager internal class PrefetchResourcesMountItem( - private val reactApplicationContext: ReactApplicationContext, + private val surfaceId: Int, private val componentName: String, private val params: ReadableMapBuffer, ) : MountItem { @OptIn(UnstableReactNativeAPI::class, FrameworkAPI::class) override fun execute(mountingManager: MountingManager) { - mountingManager.experimental_prefetchResources( - reactApplicationContext, - componentName, - params, - ) + mountingManager + .getSurfaceManager(surfaceId) + ?.experimental_prefetchResources( + surfaceId, + componentName, + params, + ) } - override fun getSurfaceId(): Int = -1 /* unused */ + override fun getSurfaceId(): Int = surfaceId override fun toString(): String = "PrefetchResourcesMountItem" } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java index 29aa5527ee08..21ad933c7aa8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java @@ -487,11 +487,13 @@ public void onSurfaceStopped(int surfaceId) { *

Subclasses can override this method to implement custom resource prefetching for the * ViewManager. * + * @param surfaceId surface ID * @param reactContext {@link com.facebook.react.bridge.ReactContext} used for the view. * @param params {@link MapBuffer} prefetch request params defined in C++ */ @UnstableReactNativeAPI - public void experimental_prefetchResources(ReactContext reactContext, MapBuffer params) { + public void experimental_prefetchResources( + int surfaceId, ReactContext reactContext, MapBuffer params) { return; } diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp index 79a47d97d4a9..01e2983223df 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp @@ -44,9 +44,8 @@ ImageRequest ImageFetcher::requestImage( SurfaceId surfaceId, const ImageRequestParams& imageRequestParams, Tag tag) { - items_.emplace_back(ImageRequestItem{ + items_[surfaceId].emplace_back(ImageRequestItem{ .imageSource = imageSource, - .surfaceId = surfaceId, .imageRequestParams = imageRequestParams, .tag = tag}); @@ -68,13 +67,18 @@ RootShadowNode::Unshared ImageFetcher::shadowTreeWillCommit( contextContainer_->at>("FabricUIManager"); static auto prefetchResources = fabricUIManager_->getClass() - ->getMethod( + ->getMethod( "experimental_prefetchResources"); - auto readableMapBuffer = - JReadableMapBuffer::createWithContents(serializeImageRequests(items_)); + for (auto& [surfaceId, surfaceImageRequests] : items_) { + auto readableMapBuffer = JReadableMapBuffer::createWithContents( + serializeImageRequests(surfaceImageRequests)); + prefetchResources( + fabricUIManager_, surfaceId, "RCTImageView", readableMapBuffer.get()); + } + items_.clear(); - prefetchResources(fabricUIManager_, "RCTImageView", readableMapBuffer.get()); return newRootShadowNode; } diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.h index 8085332dc6d7..5d69620679f4 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include namespace facebook::react { @@ -43,7 +45,7 @@ class ImageFetcher : public UIManagerCommitHook { const ShadowTree::CommitOptions& commitOptions) noexcept override; private: - std::vector items_; + std::unordered_map> items_; std::shared_ptr contextContainer_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h index a192fabc00f7..8efb0e4a6698 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h @@ -93,7 +93,6 @@ class ImageRequestParams { struct ImageRequestItem { ImageSource imageSource; - SurfaceId surfaceId{}; ImageRequestParams imageRequestParams; Tag tag{}; }; diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/conversions.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/conversions.h index b0cc007cef55..2c1de03e99d7 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/conversions.h @@ -34,8 +34,7 @@ constexpr MapBuffer::Key IS_KEY_FADE_DURATION = 11; constexpr MapBuffer::Key IS_KEY_PROGRESSIVE_RENDERING_ENABLED = 12; constexpr MapBuffer::Key IS_KEY_LOADING_INDICATOR_SRC = 13; constexpr MapBuffer::Key IS_KEY_ANALYTIC_TAG = 14; -constexpr MapBuffer::Key IS_KEY_SURFACE_ID = 15; -constexpr MapBuffer::Key IS_KEY_TAG = 16; +constexpr MapBuffer::Key IS_KEY_TAG = 15; inline void serializeImageSource( MapBufferBuilder& builder, @@ -85,7 +84,6 @@ inline MapBuffer serializeImageRequest(const ImageRequestItem& item) { auto builder = MapBufferBuilder(); serializeImageSource(builder, item.imageSource); serializeImageRequestParams(builder, item.imageRequestParams); - builder.putInt(IS_KEY_SURFACE_ID, item.surfaceId); builder.putInt(IS_KEY_TAG, item.tag); return builder.build(); } From 7aef79bd7822dafcf053efe0ab8e827adc1ad05e Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 3 Sep 2025 03:05:31 -0700 Subject: [PATCH 0024/1110] Remove UNSAFE-ALLOW-SUBPATHS exports condition (#53566) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53566 TLDR; we never advertised this and it's not in use. We have an updated incoming plan for exposing internal private code to Expo / other frameworks. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D81490655 fbshipit-source-id: f3d64582f5e6092e4928865d868ea26867ee7e47 --- packages/react-native/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index b51206a32753..496937c93e54 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -31,13 +31,11 @@ "exports": { ".": { "react-native-strict-api": "./types_generated/index.d.ts", - "react-native-strict-api-UNSAFE-ALLOW-SUBPATHS": "./types_generated/index.d.ts", "types": "./types/index.d.ts", "default": "./index.js" }, "./*": { "react-native-strict-api": null, - "react-native-strict-api-UNSAFE-ALLOW-SUBPATHS": "./types_generated/*.d.ts", "types": "./*.d.ts", "default": "./*.js" }, @@ -52,7 +50,6 @@ "./scripts/*": "./scripts/*", "./src/*": { "react-native-strict-api": null, - "react-native-strict-api-UNSAFE-ALLOW-SUBPATHS": "./types_generated/src/*.d.ts", "default": "./src/*.js" }, "./types/*.d.ts": { From 9fbce3eff18060f16e796badc415ba733ede19af Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Wed, 3 Sep 2025 03:55:49 -0700 Subject: [PATCH 0025/1110] Fix build from source for 0.82 due to Gradle 9.0 (#53560) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53560 Since Gradle 9.0, all the projects in the path must have an existing folder. As we build :packages:react-native:ReactAndroid, we need to declare the folders for :packages and :packages:react-native as well as otherwise the build from source will fail with a missing folder exception. Changelog: [Android] [Fixed] - Fix build from source due to missing folder error on Gradle 9.0 Reviewed By: fabriziocucci Differential Revision: D81482789 fbshipit-source-id: 609b503755486e10060a0f321bd0a38bd71864a1 --- packages/react-native/settings.gradle.kts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/react-native/settings.gradle.kts b/packages/react-native/settings.gradle.kts index 283d16b5f751..2036e0f16450 100644 --- a/packages/react-native/settings.gradle.kts +++ b/packages/react-native/settings.gradle.kts @@ -29,3 +29,12 @@ include(":packages:react-native:ReactAndroid:hermes-engine") project(":packages:react-native:ReactAndroid:hermes-engine").projectDir = file("ReactAndroid/hermes-engine/") + +// Since Gradle 9.0, all the projects in the path must have an existing folder. +// As we build :packages:react-native:ReactAndroid, we need to declare the folders +// for :packages and :packages:react-native as well as otherwise the build from +// source will fail with a missing folder exception. + +project(":packages").projectDir = file("/tmp") + +project(":packages:react-native").projectDir = file("/tmp") From 3895831c2bc83faf68223bb2a491e796d2799b24 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 3 Sep 2025 04:41:32 -0700 Subject: [PATCH 0026/1110] ship releaseImageDataWhenConsumed (#53576) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53576 ## Changelog: [iOS] [Fixed] - Images are removed from memory more aggressively to prevent OOMs Reviewed By: rshest Differential Revision: D81490116 fbshipit-source-id: d6b12af2d80e1c0a9ab3c624a549088b300feb3e --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +-- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +---- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +---- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +----- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +-- .../featureflags/ReactNativeFeatureFlags.h | 7 +-- .../ReactNativeFeatureFlagsAccessor.cpp | 54 +++++++------------ .../ReactNativeFeatureFlagsAccessor.h | 6 +-- .../ReactNativeFeatureFlagsDefaults.h | 6 +-- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +--- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +-- .../NativeReactNativeFeatureFlags.h | 4 +- .../ImageResponseObserverCoordinator.cpp | 8 ++- .../ReactNativeFeatureFlags.config.js | 11 ---- .../featureflags/ReactNativeFeatureFlags.js | 7 +-- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 21 files changed, 40 insertions(+), 159 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 3887895e3484..47a89ef25e6f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -336,12 +336,6 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun preventShadowTreeCommitExhaustion(): Boolean = accessor.preventShadowTreeCommitExhaustion() - /** - * Releases the cached image data when it is consumed by the observers. - */ - @JvmStatic - public fun releaseImageDataWhenConsumed(): Boolean = accessor.releaseImageDataWhenConsumed() - /** * Function used to enable / disable Pressibility from using W3C Pointer Events for its hover callbacks */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index c62dae26aea3..0e74d4361295 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7dbb21bddd9c2ae447c83923a25372e4>> + * @generated SignedSource<<37203dffb9421d1036aaeaeaa7319e28>> */ /** @@ -71,7 +71,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var perfMonitorV2EnabledCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null private var preventShadowTreeCommitExhaustionCache: Boolean? = null - private var releaseImageDataWhenConsumedCache: Boolean? = null private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null private var sweepActiveTouchOnChildNativeGesturesAndroidCache: Boolean? = null @@ -549,15 +548,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } - override fun releaseImageDataWhenConsumed(): Boolean { - var cached = releaseImageDataWhenConsumedCache - if (cached == null) { - cached = ReactNativeFeatureFlagsCxxInterop.releaseImageDataWhenConsumed() - releaseImageDataWhenConsumedCache = cached - } - return cached - } - override fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean { var cached = shouldPressibilityUseW3CPointerEventsForHoverCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index c197fee37e5b..1702ddfcd052 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4f93b4eb8f3cb427d3b926ffa18f9f9b>> + * @generated SignedSource<<9c0acc876e3205fe2ea181e71eb512c9>> */ /** @@ -130,8 +130,6 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustion(): Boolean - @DoNotStrip @JvmStatic public external fun releaseImageDataWhenConsumed(): Boolean - @DoNotStrip @JvmStatic public external fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean @DoNotStrip @JvmStatic public external fun skipActivityIdentityAssertionOnHostPause(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index eb4455840e88..73b1b91314da 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<731176315c5589990865a0995f8b7e20>> + * @generated SignedSource<<05bfed9fc7131062c8b16246986fc999>> */ /** @@ -125,8 +125,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun preventShadowTreeCommitExhaustion(): Boolean = false - override fun releaseImageDataWhenConsumed(): Boolean = false - override fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean = false override fun skipActivityIdentityAssertionOnHostPause(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 928411609dc3..e6b98e04e655 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7af6546cb6e02afcfca90635b3cbe603>> + * @generated SignedSource<<9a18369464f81c3d03f2702716dfdb29>> */ /** @@ -75,7 +75,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var perfMonitorV2EnabledCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null private var preventShadowTreeCommitExhaustionCache: Boolean? = null - private var releaseImageDataWhenConsumedCache: Boolean? = null private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null private var sweepActiveTouchOnChildNativeGesturesAndroidCache: Boolean? = null @@ -604,16 +603,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } - override fun releaseImageDataWhenConsumed(): Boolean { - var cached = releaseImageDataWhenConsumedCache - if (cached == null) { - cached = currentProvider.releaseImageDataWhenConsumed() - accessedFeatureFlags.add("releaseImageDataWhenConsumed") - releaseImageDataWhenConsumedCache = cached - } - return cached - } - override fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean { var cached = shouldPressibilityUseW3CPointerEventsForHoverCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index ce33940cffcf..56e83f450650 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9a982d4167179619629627529916259b>> + * @generated SignedSource<<845b2ee5edc9aedbdbd052d9a930f666>> */ /** @@ -125,8 +125,6 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun preventShadowTreeCommitExhaustion(): Boolean - @DoNotStrip public fun releaseImageDataWhenConsumed(): Boolean - @DoNotStrip public fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean @DoNotStrip public fun skipActivityIdentityAssertionOnHostPause(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 566dcc1a638b..7136900e64f0 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6e451f1b402ee9f80e86d0ee6b808f05>> + * @generated SignedSource<> */ /** @@ -345,12 +345,6 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } - bool releaseImageDataWhenConsumed() override { - static const auto method = - getReactNativeFeatureFlagsProviderJavaClass()->getMethod("releaseImageDataWhenConsumed"); - return method(javaProvider_); - } - bool shouldPressibilityUseW3CPointerEventsForHover() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("shouldPressibilityUseW3CPointerEventsForHover"); @@ -712,11 +706,6 @@ bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion( return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(); } -bool JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed( - facebook::jni::alias_ref /*unused*/) { - return ReactNativeFeatureFlags::releaseImageDataWhenConsumed(); -} - bool JReactNativeFeatureFlagsCxxInterop::shouldPressibilityUseW3CPointerEventsForHover( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover(); @@ -986,9 +975,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "preventShadowTreeCommitExhaustion", JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion), - makeNativeMethod( - "releaseImageDataWhenConsumed", - JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed), makeNativeMethod( "shouldPressibilityUseW3CPointerEventsForHover", JReactNativeFeatureFlagsCxxInterop::shouldPressibilityUseW3CPointerEventsForHover), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 534d8e55737e..559bf56b3e3b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<81d70a5d005d308fdc3df3d0e7ec69b9>> + * @generated SignedSource<<31298767c1dd669d2a755e67edacc911>> */ /** @@ -183,9 +183,6 @@ class JReactNativeFeatureFlagsCxxInterop static bool preventShadowTreeCommitExhaustion( facebook::jni::alias_ref); - static bool releaseImageDataWhenConsumed( - facebook::jni::alias_ref); - static bool shouldPressibilityUseW3CPointerEventsForHover( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index ee2319de5b7e..b6eed59ab63a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<9a538d3ebb58173e7e4e34453b71454b>> */ /** @@ -230,10 +230,6 @@ bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion() { return getAccessor().preventShadowTreeCommitExhaustion(); } -bool ReactNativeFeatureFlags::releaseImageDataWhenConsumed() { - return getAccessor().releaseImageDataWhenConsumed(); -} - bool ReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover() { return getAccessor().shouldPressibilityUseW3CPointerEventsForHover(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 8cfe42f56c61..be6ac38c3d63 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<83c1c5b181d47f4a54cb097f8d31c48a>> + * @generated SignedSource<<3321d357fe5c74fa42c2d0b15a744f87>> */ /** @@ -294,11 +294,6 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool preventShadowTreeCommitExhaustion(); - /** - * Releases the cached image data when it is consumed by the observers. - */ - RN_EXPORT static bool releaseImageDataWhenConsumed(); - /** * Function used to enable / disable Pressibility from using W3C Pointer Events for its hover callbacks */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 23e318880729..120375bd4e41 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5a84827d553809907a015b01cc5cff66>> + * @generated SignedSource<<041526548ef83b4afb12229a0345e29e>> */ /** @@ -947,24 +947,6 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { return flagValue.value(); } -bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() { - auto flagValue = releaseImageDataWhenConsumed_.load(); - - if (!flagValue.has_value()) { - // This block is not exclusive but it is not necessary. - // If multiple threads try to initialize the feature flag, we would only - // be accessing the provider multiple times but the end state of this - // instance and the returned flag value would be the same. - - markFlagAsAccessed(51, "releaseImageDataWhenConsumed"); - - flagValue = currentProvider_->releaseImageDataWhenConsumed(); - releaseImageDataWhenConsumed_ = flagValue; - } - - return flagValue.value(); -} - bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHover() { auto flagValue = shouldPressibilityUseW3CPointerEventsForHover_.load(); @@ -974,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(51, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -992,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(52, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1010,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(53, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1028,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(54, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1046,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(55, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1064,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(56, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1082,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useFabricInterop"); + markFlagAsAccessed(57, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1100,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(58, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1118,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(59, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1136,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(60, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1154,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(61, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1172,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "useRawPropsJsiValue"); + markFlagAsAccessed(62, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1190,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "useShadowNodeStateOnClone"); + markFlagAsAccessed(63, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1208,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "useTurboModuleInterop"); + markFlagAsAccessed(64, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1226,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "useTurboModules"); + markFlagAsAccessed(65, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1244,7 +1226,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "virtualViewHysteresisRatio"); + markFlagAsAccessed(66, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1262,7 +1244,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "virtualViewPrerenderRatio"); + markFlagAsAccessed(67, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 09227a0307ed..8e8d3c294e4f 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3e2e6e95c530a18cfd248908e99d6878>> + * @generated SignedSource<<9c51131fae9f19d03050be0c74ca3d4b>> */ /** @@ -83,7 +83,6 @@ class ReactNativeFeatureFlagsAccessor { bool perfMonitorV2Enabled(); double preparedTextCacheSize(); bool preventShadowTreeCommitExhaustion(); - bool releaseImageDataWhenConsumed(); bool shouldPressibilityUseW3CPointerEventsForHover(); bool skipActivityIdentityAssertionOnHostPause(); bool sweepActiveTouchOnChildNativeGesturesAndroid(); @@ -112,7 +111,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 69> accessedFeatureFlags_; + std::array, 68> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -165,7 +164,6 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> perfMonitorV2Enabled_; std::atomic> preparedTextCacheSize_; std::atomic> preventShadowTreeCommitExhaustion_; - std::atomic> releaseImageDataWhenConsumed_; std::atomic> shouldPressibilityUseW3CPointerEventsForHover_; std::atomic> skipActivityIdentityAssertionOnHostPause_; std::atomic> sweepActiveTouchOnChildNativeGesturesAndroid_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 7374f24f010b..0c29a6ef23c2 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1285e1c404d7a9554a5285ac3dc63cd1>> + * @generated SignedSource<<5f9c3ecf7887653fd5348a05391ab7d0>> */ /** @@ -231,10 +231,6 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } - bool releaseImageDataWhenConsumed() override { - return false; - } - bool shouldPressibilityUseW3CPointerEventsForHover() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index ee6e163db8a1..1a373688ac0b 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<027cef9dd44f14a71be7c0d1b90238b3>> */ /** @@ -504,15 +504,6 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustion(); } - bool releaseImageDataWhenConsumed() override { - auto value = values_["releaseImageDataWhenConsumed"]; - if (!value.isNull()) { - return value.getBool(); - } - - return ReactNativeFeatureFlagsDefaults::releaseImageDataWhenConsumed(); - } - bool shouldPressibilityUseW3CPointerEventsForHover() override { auto value = values_["shouldPressibilityUseW3CPointerEventsForHover"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 478a9ce0b717..837e85c6fd3b 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<31a4308a13ed1445409f3e5edfd0d388>> + * @generated SignedSource<> */ /** @@ -76,7 +76,6 @@ class ReactNativeFeatureFlagsProvider { virtual bool perfMonitorV2Enabled() = 0; virtual double preparedTextCacheSize() = 0; virtual bool preventShadowTreeCommitExhaustion() = 0; - virtual bool releaseImageDataWhenConsumed() = 0; virtual bool shouldPressibilityUseW3CPointerEventsForHover() = 0; virtual bool skipActivityIdentityAssertionOnHostPause() = 0; virtual bool sweepActiveTouchOnChildNativeGesturesAndroid() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index de00df10bb76..624afc05f7f9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<40877a198ee2fbcbed13d85abc6f793d>> + * @generated SignedSource<<07df05bf452f78603bbf79594aa0cba0>> */ /** @@ -299,11 +299,6 @@ bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustion( return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(); } -bool NativeReactNativeFeatureFlags::releaseImageDataWhenConsumed( - jsi::Runtime& /*runtime*/) { - return ReactNativeFeatureFlags::releaseImageDataWhenConsumed(); -} - bool NativeReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 02644d780bbd..4b1d9860ee40 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<77f248a7c43e9f11f6e727319a72e1a0>> + * @generated SignedSource<<228d7484ef89bb01fee8dcff2657c56a>> */ /** @@ -138,8 +138,6 @@ class NativeReactNativeFeatureFlags bool preventShadowTreeCommitExhaustion(jsi::Runtime& runtime); - bool releaseImageDataWhenConsumed(jsi::Runtime& runtime); - bool shouldPressibilityUseW3CPointerEventsForHover(jsi::Runtime& runtime); bool skipActivityIdentityAssertionOnHostPause(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageResponseObserverCoordinator.cpp b/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageResponseObserverCoordinator.cpp index bbe23fa9565a..7328c45f20b1 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageResponseObserverCoordinator.cpp +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/ImageResponseObserverCoordinator.cpp @@ -123,11 +123,9 @@ void ImageResponseObserverCoordinator::nativeImageResponseFailed( } void ImageResponseObserverCoordinator::consumeResponse() const { - if (ReactNativeFeatureFlags::releaseImageDataWhenConsumed()) { - status_ = ImageResponse::Status::Consumed; - imageData_.reset(); - imageMetadata_.reset(); - } + status_ = ImageResponse::Status::Consumed; + imageData_.reset(); + imageMetadata_.reset(); } } // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 5b893d3daba7..d2116260b4fb 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -591,17 +591,6 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'experimental', }, - releaseImageDataWhenConsumed: { - defaultValue: false, - metadata: { - dateAdded: '2025-07-10', - description: - 'Releases the cached image data when it is consumed by the observers.', - expectedReleaseValue: true, - purpose: 'experimentation', - }, - ossReleaseStage: 'none', - }, shouldPressibilityUseW3CPointerEventsForHover: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 69e673413d14..08f2f5faaeb9 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<22523248acc3e378f3f81ef43406f106>> + * @generated SignedSource<<7de65fc90fa1275f89375fd214b94172>> * @flow strict * @noformat */ @@ -101,7 +101,6 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ perfMonitorV2Enabled: Getter, preparedTextCacheSize: Getter, preventShadowTreeCommitExhaustion: Getter, - releaseImageDataWhenConsumed: Getter, shouldPressibilityUseW3CPointerEventsForHover: Getter, skipActivityIdentityAssertionOnHostPause: Getter, sweepActiveTouchOnChildNativeGesturesAndroid: Getter, @@ -404,10 +403,6 @@ export const preparedTextCacheSize: Getter = createNativeFlagGetter('pre * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. */ export const preventShadowTreeCommitExhaustion: Getter = createNativeFlagGetter('preventShadowTreeCommitExhaustion', false); -/** - * Releases the cached image data when it is consumed by the observers. - */ -export const releaseImageDataWhenConsumed: Getter = createNativeFlagGetter('releaseImageDataWhenConsumed', false); /** * Function used to enable / disable Pressibility from using W3C Pointer Events for its hover callbacks */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 77024df92d82..ef29da894136 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6d699b7dc91fce5814db79d7f19ba511>> + * @generated SignedSource<<1e6f17de0ebb06a085d4cc563df1e32b>> * @flow strict * @noformat */ @@ -76,7 +76,6 @@ export interface Spec extends TurboModule { +perfMonitorV2Enabled?: () => boolean; +preparedTextCacheSize?: () => number; +preventShadowTreeCommitExhaustion?: () => boolean; - +releaseImageDataWhenConsumed?: () => boolean; +shouldPressibilityUseW3CPointerEventsForHover?: () => boolean; +skipActivityIdentityAssertionOnHostPause?: () => boolean; +sweepActiveTouchOnChildNativeGesturesAndroid?: () => boolean; From f170db412b3ab46fd0894d5d66431d9c230cd3a8 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Wed, 3 Sep 2025 05:34:11 -0700 Subject: [PATCH 0027/1110] Use autolinking react-native-config output in iOS artifacts generator (#53503) Summary: Resolves https://github.com/facebook/react-native/issues/53501 This is a pretty major oversight of (presumably) the old autolinking refactor. The iOS autolinking's second stage, invoked in `use_react_native!` does not accept the `react-native-config` sub-command's `react-native-config` output. This is only invoked and used in the prior step, `use_native_modules`. The second step instead invokes old code that does something _similar_ to the new autolinking in `scripts/generate-artifacts-executor`, and happens to align in most cases. (But it does "autolinking" from scratch). tl;dr: When the results don't match up, things go wrong. Instead, we now write the autolinking (react native config) results to a file, then read the output back in the second step. This doesn't affect Android/Gradle, which are implemented correctly. ## Changelog: [IOS] [FIXED] - Use autolinking-generated react-native-config output in second step of cocoapods linking that generates artifacts and generated source Pull Request resolved: https://github.com/facebook/react-native/pull/53503 Test Plan: - See https://github.com/facebook/react-native/issues/53501 for failing repro - Clone for working repro: https://github.com/byCedric/react-native-codegen-ios-autolinking/tree/fix-54503 - Note: Contains this PR's changes as a patch - `bun install` - `bun expo run:ios` Reviewed By: cortinico Differential Revision: D81490755 Pulled By: cipolleschi fbshipit-source-id: eefe786a116404f4ed24bd7125dfb108a811f71e --- .../scripts/cocoapods/autolinking.rb | 6 +++ .../scripts/cocoapods/codegen_utils.rb | 2 +- .../generate-artifacts-executor/index.js | 6 ++- .../generate-artifacts-executor/utils.js | 51 +++++++++++++++---- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/packages/react-native/scripts/cocoapods/autolinking.rb b/packages/react-native/scripts/cocoapods/autolinking.rb index b2e5600bc5d3..d8e9c7cec2f5 100644 --- a/packages/react-native/scripts/cocoapods/autolinking.rb +++ b/packages/react-native/scripts/cocoapods/autolinking.rb @@ -40,6 +40,12 @@ def list_native_modules!(config_command) packages = config["dependencies"] ios_project_root = Pathname.new(config["project"]["ios"]["sourceDir"]) react_native_path = Pathname.new(config["reactNativePath"]) + codegen_output_path = ios_project_root.join("build/generated/autolinking/autolinking.json") + + # Write autolinking react-native-config output to codegen folder + FileUtils.mkdir_p(File.dirname(codegen_output_path)) + File.write(codegen_output_path, json) + found_pods = [] packages.each do |package_name, package| diff --git a/packages/react-native/scripts/cocoapods/codegen_utils.rb b/packages/react-native/scripts/cocoapods/codegen_utils.rb index 3ce12e28ebc7..3c51ad584e11 100644 --- a/packages/react-native/scripts/cocoapods/codegen_utils.rb +++ b/packages/react-native/scripts/cocoapods/codegen_utils.rb @@ -87,7 +87,7 @@ def self.clean_up_build_folder(rn_path, codegen_dir, dir_manager: Dir, file_mana codegen_path = file_manager.join(ios_folder, codegen_dir) return if !dir_manager.exist?(codegen_path) - FileUtils.rm_rf(dir_manager.glob("#{codegen_path}/*")) + FileUtils.rm_rf("#{codegen_path}") base_provider_path = file_manager.join(rn_path, 'React', 'Fabric', 'RCTThirdPartyFabricComponentsProvider') FileUtils.rm_rf("#{base_provider_path}.h") FileUtils.rm_rf("#{base_provider_path}.mm") diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js index 002273410bd6..a3c52e39f386 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js @@ -86,10 +86,14 @@ function execute( buildCodegenIfNeeded(); } - const reactNativeConfig = readReactNativeConfig(projectRoot); + const reactNativeConfig = readReactNativeConfig( + projectRoot, + baseOutputPath, + ); const codegenEnabledLibraries = findCodegenEnabledLibraries( pkgJson, projectRoot, + baseOutputPath, reactNativeConfig, ); diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js index e833a5921620..5cb42c28e70e 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js @@ -97,15 +97,40 @@ function cleanupEmptyFilesAndFolders(filepath /*: string */) { } } -function readReactNativeConfig(projectRoot /*: string */) /*: $FlowFixMe */ { - const rnConfigFilePath = path.resolve(projectRoot, 'react-native.config.js'); +function readGeneratedAutolinkingOutput( + baseOutputPath /*: string */, +) /*: $FlowFixMe */ { + // NOTE: Generated by scripts/cocoapods/autolinking.rb in list_native_modules (called by use_native_modules) + const autolinkingGeneratedPath = path.resolve( + baseOutputPath, + 'build/generated/autolinking/autolinking.json', + ); + if (fs.existsSync(autolinkingGeneratedPath)) { + // $FlowFixMe[unsupported-syntax] + return require(autolinkingGeneratedPath); + } else { + codegenLog( + `Could not find generated autolinking output at: ${autolinkingGeneratedPath}`, + ); + return null; + } +} - if (!fs.existsSync(rnConfigFilePath)) { +function readReactNativeConfig( + projectRoot /*: string */, + baseOutputPath /*: string */, +) /*: $FlowFixMe */ { + const autolinkingOutput = readGeneratedAutolinkingOutput(baseOutputPath); + const rnConfigFilePath = path.resolve(projectRoot, 'react-native.config.js'); + if (autolinkingOutput) { + return autolinkingOutput; + } else if (fs.existsSync(rnConfigFilePath)) { + // $FlowFixMe[unsupported-syntax] + return require(rnConfigFilePath); + } else { + codegenLog(`Could not find React Native config at: ${rnConfigFilePath}`); return {}; } - - // $FlowFixMe[unsupported-syntax] - return require(rnConfigFilePath); } /** @@ -114,17 +139,23 @@ function readReactNativeConfig(projectRoot /*: string */) /*: $FlowFixMe */ { function findCodegenEnabledLibraries( pkgJson /*: $FlowFixMe */, projectRoot /*: string */, + baseOutputPath /*: string */, reactNativeConfig /*: $FlowFixMe */, ) /*: Array<$FlowFixMe> */ { const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot); if (pkgJsonIncludesGeneratedCode(pkgJson)) { return projectLibraries; } else { - return [ - ...projectLibraries, - ...findExternalLibraries(pkgJson, projectRoot), + const libraries = [...projectLibraries]; + // If we ran autolinking, we shouldn't try to run our own "autolinking-like" + // library discovery + if (!readGeneratedAutolinkingOutput(baseOutputPath)) { + libraries.push(...findExternalLibraries(pkgJson, projectRoot)); + } + libraries.push( ...findLibrariesFromReactNativeConfig(projectRoot, reactNativeConfig), - ]; + ); + return libraries; } } From dc54eaebacf3a1964a70bc366ecf9061aee00403 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 3 Sep 2025 06:17:35 -0700 Subject: [PATCH 0028/1110] Decouple TimerExecutor creation from ReactInstance (#53569) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53569 Simplify construction to save a JNI call, slightly more efficient on binary size too (1KiB hah) Changelog: [Internal] Reviewed By: rshest Differential Revision: D81445834 fbshipit-source-id: b0ec84d5e04d364e34eef4c3b712c62f878325cf --- .../com/facebook/react/runtime/JSTimerExecutor.kt | 13 +++++++++---- .../com/facebook/react/runtime/ReactInstance.kt | 6 +----- .../main/jni/react/runtime/jni/JJSTimerExecutor.cpp | 6 ++++++ .../main/jni/react/runtime/jni/JJSTimerExecutor.h | 6 ++++-- .../main/jni/react/runtime/jni/JReactInstance.cpp | 8 -------- .../src/main/jni/react/runtime/jni/JReactInstance.h | 6 ------ 6 files changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.kt index 87b1a027355a..15c11a07531a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/JSTimerExecutor.kt @@ -7,15 +7,20 @@ package com.facebook.react.runtime -import com.facebook.jni.HybridData -import com.facebook.jni.annotations.DoNotStripAny +import com.facebook.jni.HybridClassBase +import com.facebook.jni.annotations.DoNotStrip import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableNativeArray import com.facebook.react.modules.core.JavaScriptTimerExecutor import com.facebook.soloader.SoLoader -@DoNotStripAny -internal class JSTimerExecutor(private val mHybridData: HybridData) : JavaScriptTimerExecutor { +@DoNotStrip +internal class JSTimerExecutor() : HybridClassBase(), JavaScriptTimerExecutor { + init { + initHybrid() + } + + private external fun initHybrid() private external fun callTimers(timerIDs: WritableNativeArray) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt index 96bb76b3b2a0..4999f8670504 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt @@ -76,7 +76,6 @@ import java.util.ArrayList import java.util.HashMap import java.util.HashSet import kotlin.collections.Collection -import kotlin.jvm.JvmStatic /** * A replacement for [com.facebook.react.bridge.CatalystInstance] responsible for creating and @@ -126,7 +125,7 @@ internal class ReactInstance( ReactChoreographer.initialize(AndroidChoreographerProvider.getInstance()) devSupportManager.startInspector() - val jsTimerExecutor = createJSTimerExecutor() + val jsTimerExecutor = JSTimerExecutor() javaTimerManager = JavaTimerManager( context, @@ -182,7 +181,6 @@ internal class ReactInstance( getJSCallInvokerHolder(), getNativeMethodCallInvokerHolder(), ) - Systrace.endSection(Systrace.TRACE_TAG_REACT) // Set up Fabric @@ -633,7 +631,5 @@ internal class ReactInstance( SystraceMessage.endSection(Systrace.TRACE_TAG_REACT).flush() } } - - @JvmStatic @DoNotStrip private external fun createJSTimerExecutor(): JSTimerExecutor } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.cpp index 0e2a390b9e4f..f6aed2d17dba 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.cpp @@ -12,6 +12,11 @@ namespace facebook::react { +void JJSTimerExecutor::initHybrid( + jni::alias_ref jobj) { + setCxxInstance(jobj); +} + void JJSTimerExecutor::setTimerManager( std::weak_ptr timerManager) { timerManager_ = timerManager; @@ -28,6 +33,7 @@ void JJSTimerExecutor::callTimers(WritableNativeArray* timerIDs) { void JJSTimerExecutor::registerNatives() { registerHybrid({ makeNativeMethod("callTimers", JJSTimerExecutor::callTimers), + makeNativeMethod("initHybrid", JJSTimerExecutor::initHybrid), }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.h b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.h index cc7d93850654..a3b89ebb6f64 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JJSTimerExecutor.h @@ -17,18 +17,20 @@ namespace facebook::react { class JJSTimerExecutor : public jni::HybridClass { public: - JJSTimerExecutor() = default; - constexpr static auto kJavaDescriptor = "Lcom/facebook/react/runtime/JSTimerExecutor;"; static void registerNatives(); + static void initHybrid(jni::alias_ref jobj); + void setTimerManager(std::weak_ptr timerManager); void callTimers(WritableNativeArray* timerIDs); private: + JJSTimerExecutor() = default; + friend HybridBase; std::weak_ptr timerManager_; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp index 5ffd7492ba81..f0b5e62826a1 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp @@ -168,12 +168,6 @@ JReactInstance::getNativeMethodCallInvokerHolder() { return nativeMethodCallInvokerHolder_; } -jni::global_ref -JReactInstance::createJSTimerExecutor( - jni::alias_ref /* unused */) { - return jni::make_global(JJSTimerExecutor::newObjectCxxArgs()); -} - void JReactInstance::callFunctionOnModule( const std::string& moduleName, const std::string& methodName, @@ -217,8 +211,6 @@ void JReactInstance::unregisterFromInspector() { void JReactInstance::registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", JReactInstance::initHybrid), - makeNativeMethod( - "createJSTimerExecutor", JReactInstance::createJSTimerExecutor), makeNativeMethod( "loadJSBundleFromAssets", JReactInstance::loadJSBundleFromAssets), makeNativeMethod( diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h index d6552a8a9789..2b42b70507ef 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h @@ -50,12 +50,6 @@ class JReactInstance : public jni::HybridClass { jni::alias_ref jReactHostInspectorTarget); - /* - * Instantiates and returns an instance of `JSTimerExecutor`. - */ - static jni::global_ref createJSTimerExecutor( - jni::alias_ref /* unused */); - static void registerNatives(); void loadJSBundleFromAssets( From 2ed6a08ef36358ff813ea5a3334909c774e79092 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 3 Sep 2025 06:17:35 -0700 Subject: [PATCH 0029/1110] Mark JavaTimerManager idle callback methods as @LegacyArchitecture (#53570) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53570 Idle callbacks are implemented as a C++ module in the new architecture, this code should not be used. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D81485912 fbshipit-source-id: 18103bb96441880ff3de423aa6c03a176f6ff5de --- .../com/facebook/react/modules/core/JavaTimerManager.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt index b94459609275..973d5bc847c6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt @@ -18,6 +18,7 @@ import com.facebook.react.bridge.WritableArray import com.facebook.react.common.SystemClock.currentTimeMillis import com.facebook.react.common.SystemClock.nanoTime import com.facebook.react.common.SystemClock.uptimeMillis +import com.facebook.react.common.annotations.internal.LegacyArchitecture import com.facebook.react.devsupport.interfaces.DevSupportManager import com.facebook.react.jstasks.HeadlessJsTaskContext import com.facebook.react.jstasks.HeadlessJsTaskEventListener @@ -110,6 +111,7 @@ public open class JavaTimerManager( clearChoreographerIdleCallback() } + @LegacyArchitecture private fun maybeSetChoreographerIdleCallback() { synchronized(idleCallbackGuard) { if (sendIdleEvents) { @@ -118,6 +120,7 @@ public open class JavaTimerManager( } } + @LegacyArchitecture private fun maybeIdleCallback() { if (isPaused.get() && !isRunningTasks.get()) { clearFrameCallback() @@ -145,6 +148,7 @@ public open class JavaTimerManager( } } + @LegacyArchitecture private fun setChoreographerIdleCallback() { if (!frameIdleCallbackPosted) { reactChoreographer.postFrameCallback( @@ -155,6 +159,7 @@ public open class JavaTimerManager( } } + @LegacyArchitecture private fun clearChoreographerIdleCallback() { if (frameIdleCallbackPosted) { reactChoreographer.removeFrameCallback( @@ -235,6 +240,7 @@ public open class JavaTimerManager( } @DoNotStrip + @LegacyArchitecture public open fun setSendIdleEvents(sendIdleEvents: Boolean) { synchronized(idleCallbackGuard) { this.sendIdleEvents = sendIdleEvents } UiThreadUtil.runOnUiThread { @@ -328,6 +334,7 @@ public open class JavaTimerManager( } } + @LegacyArchitecture private inner class IdleCallbackRunnable(private val frameStartTime: Long) : Runnable { @Volatile private var isCancelled = false From 87a1b510b769226acaeafb30116f62744a7a8ff3 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Wed, 3 Sep 2025 06:24:23 -0700 Subject: [PATCH 0030/1110] Fix build with Cocopaods and Dynamic frameworks (#53367) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53367 We are missing a dependency in the React-jsinspector podspec that prevents React Native from building with dynamic frameworks. ## Changelog: [Internal] - Reviewed By: cortinico Differential Revision: D80619664 fbshipit-source-id: 1c87ef4d3614ceea3a23196831479ecae0a5acc8 --- .../ReactCommon/jsinspector-modern/React-jsinspector.podspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec b/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec index 78d77ffa539a..9fd5a0816452 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec @@ -54,6 +54,8 @@ Pod::Spec.new do |s| add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork') add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing') s.dependency "React-perflogger", version + add_dependency(s, "React-oscompat") + if use_hermes() s.dependency "hermes-engine" end From 3a0c402d26c366126fe7b36b2d504be4f658d68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 3 Sep 2025 07:07:39 -0700 Subject: [PATCH 0031/1110] fix(iOS): modal swipe dismissal works only for the first time (#53499) Summary: This PR fixes swipe dismissal to work each time the modal is shown. Previously modalInPresentation was set on the view controller which gets destroyed every time user dismisses the modal. This makes sure that modal in presentation is correctly preserved when showing multiple modals. https://github.com/user-attachments/assets/c7f140e5-1c4f-4809-8453-148d4becc9eb ## Changelog: [IOS] [FIXED] - modal swipe dismissal works only for the first time Pull Request resolved: https://github.com/facebook/react-native/pull/53499 Test Plan: 1. Open RN Tester 2. Check allow swipe dismissal 3. Check closing it multiple times Reviewed By: javache Differential Revision: D81312918 Pulled By: cipolleschi fbshipit-source-id: 4f7cc60762660e5d5310f4973fe8df340c1ba52b --- .../ComponentViews/Modal/RCTModalHostViewComponentView.mm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm index afb2016fe970..bbba4cd45ee6 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm @@ -106,6 +106,7 @@ @implementation RCTModalHostViewComponentView { BOOL _shouldAnimatePresentation; BOOL _shouldPresent; BOOL _isPresented; + BOOL _modalInPresentation; } - (instancetype)initWithFrame:(CGRect)frame @@ -115,6 +116,7 @@ - (instancetype)initWithFrame:(CGRect)frame _shouldAnimatePresentation = YES; _isPresented = NO; + _modalInPresentation = YES; } return self; @@ -126,7 +128,7 @@ - (RCTFabricModalHostViewController *)viewController _viewController = [RCTFabricModalHostViewController new]; _viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; _viewController.delegate = self; - _viewController.modalInPresentation = YES; + _viewController.modalInPresentation = _modalInPresentation; } return _viewController; } @@ -152,6 +154,7 @@ - (void)ensurePresentedOnlyIfNeeded if (shouldBePresented) { [self saveAccessibilityFocusedView]; self.viewController.presentationController.delegate = self; + self.viewController.modalInPresentation = _modalInPresentation; _isPresented = YES; [self presentViewController:self.viewController @@ -276,7 +279,8 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.viewController.modalPresentationStyle = presentationConfiguration(newProps); if (oldViewProps.allowSwipeDismissal != newProps.allowSwipeDismissal) { - self.viewController.modalInPresentation = !newProps.allowSwipeDismissal; + _modalInPresentation = !newProps.allowSwipeDismissal; + self.viewController.modalInPresentation = _modalInPresentation; } _shouldPresent = newProps.visible; From cc83f6e84ac262ff298352ee18f1e32ab40d2f4b Mon Sep 17 00:00:00 2001 From: Christian Kruse Date: Wed, 3 Sep 2025 07:11:08 -0700 Subject: [PATCH 0032/1110] Fix extra semi colon (#53483) Summary: Changelog: [Internal] Fix extra semi colon warning Pull Request resolved: https://github.com/facebook/react-native/pull/53483 Reviewed By: cortinico, kuwerty Differential Revision: D81032496 fbshipit-source-id: da7d6f8355fd6ae228033a380d38d677632bafaf --- .../ReactCommon/jsinspector-modern/ScopedExecutor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ScopedExecutor.h b/packages/react-native/ReactCommon/jsinspector-modern/ScopedExecutor.h index 6ede8b356d9d..2c848fd63a6e 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/ScopedExecutor.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/ScopedExecutor.h @@ -94,4 +94,4 @@ class EnableExecutorFromThis : public std::enable_shared_from_this { VoidExecutor baseExecutor_; }; -}; // namespace facebook::react::jsinspector_modern +} // namespace facebook::react::jsinspector_modern From 2cd06ad69a4b2266bcd39be08be05a76a26be081 Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Wed, 3 Sep 2025 07:38:09 -0700 Subject: [PATCH 0033/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53582) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53582 Reviewed By: rshest Differential Revision: D81567762 fbshipit-source-id: af4e8cc78675b0941a3fb41a7c0eb6f08dc728c1 --- .../react-native/React/CxxBridge/RCTMessageThread.mm | 2 +- .../react-native/React/CxxBridge/RCTObjcExecutor.mm | 4 ++-- packages/react-native/React/CxxModule/RCTCxxMethod.mm | 2 +- .../react-native/React/CxxModule/RCTNativeModule.mm | 10 +++++----- .../React/DevSupport/RCTInspectorDevServerHelper.mm | 8 ++++---- .../React/DevSupport/RCTInspectorNetworkHelper.mm | 4 ++-- .../DevSupport/RCTPausedInDebuggerOverlayController.mm | 2 +- .../Modal/RCTFabricModalHostViewController.mm | 4 ++-- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/react-native/React/CxxBridge/RCTMessageThread.mm b/packages/react-native/React/CxxBridge/RCTMessageThread.mm index d234aaaa8cfd..d337150289a1 100644 --- a/packages/react-native/React/CxxBridge/RCTMessageThread.mm +++ b/packages/react-native/React/CxxBridge/RCTMessageThread.mm @@ -66,7 +66,7 @@ void RCTMessageThread::tryFunc(const std::function &func) { NSError *error = tryAndReturnError(func); - if (error) { + if (error != nullptr) { m_errorBlock(error); } } diff --git a/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm b/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm index 94976166fa92..15afcb68a0a4 100644 --- a/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm +++ b/packages/react-native/React/CxxBridge/RCTObjcExecutor.mm @@ -41,7 +41,7 @@ : m_jse(jse), m_errorBlock(errorBlock), m_delegate(std::move(delegate)), m_jsThread(std::move(jsThread)) { m_jsCallback = ^(id json, NSError *error) { - if (error) { + if (error != nullptr) { // Do not use "m_errorBlock" here as the bridge might be in the middle // of invalidation as a result of error handling and "this" can be // already deallocated. @@ -81,7 +81,7 @@ void loadBundle(std::unique_ptr script, std::string sourceURL onComplete:^(NSError *error) { RCTProfileEndFlowEvent(); - if (error) { + if (error != nullptr) { m_errorBlock(error); return; } diff --git a/packages/react-native/React/CxxModule/RCTCxxMethod.mm b/packages/react-native/React/CxxModule/RCTCxxMethod.mm index c87f79dffb99..f823e7d7880a 100644 --- a/packages/react-native/React/CxxModule/RCTCxxMethod.mm +++ b/packages/react-native/React/CxxModule/RCTCxxMethod.mm @@ -26,7 +26,7 @@ @implementation RCTCxxMethod { - (instancetype)initWithCxxMethod:(const CxxModule::Method &)method { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _method = std::make_unique(method); } return self; diff --git a/packages/react-native/React/CxxModule/RCTNativeModule.mm b/packages/react-native/React/CxxModule/RCTNativeModule.mm index 6150f31bf0c0..b0f45eb1588a 100644 --- a/packages/react-native/React/CxxModule/RCTNativeModule.mm +++ b/packages/react-native/React/CxxModule/RCTNativeModule.mm @@ -76,7 +76,7 @@ static MethodCallResult invokeInner( void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int callId) { id method = m_moduleData.methods[methodId]; - if (method) { + if (method != nullptr) { RCT_PROFILE_BEGIN_EVENT( RCTProfileTagAlways, @"[RCTNativeModule invoke]", @@ -119,13 +119,13 @@ static MethodCallResult invokeInner( if (isSyncModule) { block(); BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName); - } else if (queue) { + } else if (queue != nullptr) { BridgeNativeModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName); dispatch_async(queue, block); } #ifdef RCT_DEV - if (!queue) { + if (queue == nullptr) { RCTLog( @"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) without a method queue.", methodId, @@ -153,7 +153,7 @@ static MethodCallResult invokeInner( int callId, SchedulingContext context) { - if (!bridge || !bridge.valid || !moduleData) { + if ((bridge == nullptr) || !bridge.valid || (moduleData == nullptr)) { if (context == Sync) { /** * NOTE: moduleName and methodName are "". This shouldn't be an issue because there can only be one ongoing sync @@ -166,7 +166,7 @@ static MethodCallResult invokeInner( } id method = moduleData.methods[methodId]; - if (RCT_DEBUG && !method) { + if (RCT_DEBUG && (method == nullptr)) { RCTLogError(@"Unknown methodID: %ud for module: %@", methodId, moduleData.name); } diff --git a/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm b/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm index a9aedcaddf87..9230dd3b3322 100644 --- a/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm +++ b/packages/react-native/React/DevSupport/RCTInspectorDevServerHelper.mm @@ -24,14 +24,14 @@ { NSNumber *port = @8081; NSString *portStr = [[[NSProcessInfo processInfo] environment] objectForKey:@"RCT_METRO_PORT"]; - if (portStr && [portStr length] > 0) { + if ((portStr != nullptr) && [portStr length] > 0) { port = [NSNumber numberWithInt:[portStr intValue]]; } - if ([bundleURL port]) { + if ([bundleURL port] != nullptr) { port = [bundleURL port]; } NSString *host = [bundleURL host]; - if (!host) { + if (host == nullptr) { host = @"localhost"; } @@ -186,7 +186,7 @@ + (void)disableDebugger NSString *key = [inspectorURL absoluteString]; id connection = socketConnections[key]; - if (!connection || !connection.isConnected) { + if ((connection == nullptr) || !connection.isConnected) { connection = [[RCTCxxInspectorPackagerConnection alloc] initWithURL:inspectorURL]; socketConnections[key] = connection; diff --git a/packages/react-native/React/DevSupport/RCTInspectorNetworkHelper.mm b/packages/react-native/React/DevSupport/RCTInspectorNetworkHelper.mm index 53e8fde774ec..8e775d3185df 100644 --- a/packages/react-native/React/DevSupport/RCTInspectorNetworkHelper.mm +++ b/packages/react-native/React/DevSupport/RCTInspectorNetworkHelper.mm @@ -21,7 +21,7 @@ @implementation RCTInspectorNetworkHelper - (instancetype)init { self = [super init]; - if (self) { + if (self != nullptr) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; self.executorsByTaskId = [NSMutableDictionary new]; @@ -63,7 +63,7 @@ - (void)loadNetworkResourceWithParams:(const RCTInspectorLoadNetworkResourceRequ - (void)withListenerForTask:(NSURLSessionTask *)task execute:(ListenerBlock)block { void (^executor)(ListenerBlock) = self.executorsByTaskId[@(task.taskIdentifier)]; - if (executor) { + if (executor != nullptr) { executor(block); } } diff --git a/packages/react-native/React/DevSupport/RCTPausedInDebuggerOverlayController.mm b/packages/react-native/React/DevSupport/RCTPausedInDebuggerOverlayController.mm index 32697c942eea..9237213bcf3e 100644 --- a/packages/react-native/React/DevSupport/RCTPausedInDebuggerOverlayController.mm +++ b/packages/react-native/React/DevSupport/RCTPausedInDebuggerOverlayController.mm @@ -103,7 +103,7 @@ - (UIWindow *)alertWindow if (_alertWindow == nil) { _alertWindow = [[UIWindow alloc] initWithWindowScene:RCTKeyWindow().windowScene]; - if (_alertWindow) { + if (_alertWindow != nullptr) { _alertWindow.rootViewController = [UIViewController new]; _alertWindow.windowLevel = UIWindowLevelAlert + 1; } diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTFabricModalHostViewController.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTFabricModalHostViewController.mm index eb100a437d52..16b71e4d64ec 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTFabricModalHostViewController.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTFabricModalHostViewController.mm @@ -17,7 +17,7 @@ @implementation RCTFabricModalHostViewController { - (instancetype)init { - if (!(self = [super init])) { + if ((self = [super init]) == nullptr) { return nil; } _touchHandler = [RCTSurfaceTouchHandler new]; @@ -61,7 +61,7 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { UIInterfaceOrientationMask appSupportedOrientationsMask = [RCTSharedApplication() supportedInterfaceOrientationsForWindow:RCTKeyWindow()]; - if (!(_supportedInterfaceOrientations & appSupportedOrientationsMask)) { + if ((_supportedInterfaceOrientations & appSupportedOrientationsMask) == 0u) { RCTLogError( @"Modal was presented with 0x%x orientations mask but the application only supports 0x%x." @"Add more interface orientations to your app's Info.plist to fix this." From e41dce3b4e2a3e9cf114c81ab3385cc65145c89d Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Wed, 3 Sep 2025 07:59:56 -0700 Subject: [PATCH 0034/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53583) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53583 Reviewed By: rshest Differential Revision: D81575288 fbshipit-source-id: 0315c0ac759799dc9a84e68fa8d957b5547b1682 --- .../Text/TextInput/RCTBaseTextInputViewManager.mm | 2 +- .../Libraries/TypeSafety/RCTTypedModuleConstants.mm | 2 +- packages/react-native/React/Base/RCTBridgeProxy.mm | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm index e3d7d205457e..a30ba75dc59a 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm @@ -141,7 +141,7 @@ - (void)setBridge:(RCTBridge *)bridge RCTExecuteOnUIManagerQueue(^{ RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[self.bridge.uiManager shadowViewForReactTag:viewTag]; - if (value) { + if (value != nullptr) { [shadowView setText:value]; } [self.bridge.uiManager setNeedsLayout]; diff --git a/packages/react-native/Libraries/TypeSafety/RCTTypedModuleConstants.mm b/packages/react-native/Libraries/TypeSafety/RCTTypedModuleConstants.mm index a1ff4f0cbaa9..b2f45da1cf99 100644 --- a/packages/react-native/Libraries/TypeSafety/RCTTypedModuleConstants.mm +++ b/packages/react-native/Libraries/TypeSafety/RCTTypedModuleConstants.mm @@ -16,7 +16,7 @@ @implementation _RCTTypedModuleConstants { + (instancetype)newWithUnsafeDictionary:(NSDictionary *)dictionary { _RCTTypedModuleConstants *constants = [self new]; - if (constants) { + if (constants != nullptr) { constants->_dictionary = dictionary; } return constants; diff --git a/packages/react-native/React/Base/RCTBridgeProxy.mm b/packages/react-native/React/Base/RCTBridgeProxy.mm index c8ebec6b7105..9608f72bf5e0 100644 --- a/packages/react-native/React/Base/RCTBridgeProxy.mm +++ b/packages/react-native/React/Base/RCTBridgeProxy.mm @@ -53,7 +53,7 @@ - (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry launchOptions:(nullable NSDictionary *)launchOptions { self = [super self]; - if (self) { + if (self != nullptr) { _uiManagerProxy = [[RCTUIManagerProxy alloc] initWithViewRegistry:viewRegistry]; _moduleRegistry = moduleRegistry; _bundleManager = bundleManager; @@ -75,7 +75,7 @@ - (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue if (queue == RCTJSThread) { _dispatchToJSThread(block); - } else if (queue) { + } else if (queue != nullptr) { dispatch_async(queue, block); } } @@ -427,7 +427,7 @@ @implementation RCTUIManagerProxy { - (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry { self = [super self]; - if (self) { + if (self != nullptr) { _viewRegistry = viewRegistry; _legacyViewRegistry = [NSMutableDictionary new]; } @@ -443,8 +443,8 @@ - (UIView *)viewForReactTag:(NSNumber *)reactTag { [self logWarning:@"Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED." cmd:_cmd]; - UIView *view = [_viewRegistry viewForReactTag:reactTag] ? [_viewRegistry viewForReactTag:reactTag] - : [_legacyViewRegistry objectForKey:reactTag]; + UIView *view = ([_viewRegistry viewForReactTag:reactTag] != nullptr) ? [_viewRegistry viewForReactTag:reactTag] + : [_legacyViewRegistry objectForKey:reactTag]; return RCTPaperViewOrCurrentView(view); } @@ -457,7 +457,7 @@ - (void)addUIBlock:(RCTViewManagerUIBlock)block __weak __typeof(self) weakSelf = self; RCTExecuteOnMainQueue(^{ __typeof(self) strongSelf = weakSelf; - if (strongSelf) { + if (strongSelf != nullptr) { RCTUIManager *proxiedManager = (RCTUIManager *)strongSelf; RCTComposedViewRegistry *composedViewRegistry = [[RCTComposedViewRegistry alloc] initWithUIManager:proxiedManager From 8d8452173a35ccb048dd5798aa631716ea2e6539 Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Wed, 3 Sep 2025 08:12:28 -0700 Subject: [PATCH 0035/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53584) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53584 Reviewed By: rshest Differential Revision: D81565980 fbshipit-source-id: e9c7eeb3219ee56b693583a6cf8a7905ac360324 --- .../RCTSurfaceSizeMeasureMode.mm | 8 ++-- .../CoreModules/RCTActionSheetManager.mm | 20 +++++----- .../React/CoreModules/RCTAlertController.mm | 4 +- .../React/CoreModules/RCTAlertManager.mm | 4 +- .../React/CoreModules/RCTDevMenu.mm | 14 +++---- .../React/CoreModules/RCTEventDispatcher.mm | 18 +++++---- .../React/CoreModules/RCTExceptionsManager.mm | 14 +++---- .../React/CoreModules/RCTFPSGraph.mm | 6 +-- .../React/CoreModules/RCTLogBox.mm | 12 +++--- .../React/CoreModules/RCTLogBoxView.mm | 2 +- .../React/CoreModules/RCTPerfMonitor.mm | 38 +++++++++---------- 11 files changed, 71 insertions(+), 69 deletions(-) diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.mm index 8bcb5d1d9324..2b94f6cbeae0 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.mm @@ -18,17 +18,17 @@ void RCTSurfaceMinimumSizeAndMaximumSizeFromSizeAndSizeMeasureMode( *minimumSize = CGSizeZero; *maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - if (sizeMeasureMode & RCTSurfaceSizeMeasureModeWidthExact) { + if ((sizeMeasureMode & RCTSurfaceSizeMeasureModeWidthExact) != 0) { minimumSize->width = size.width; maximumSize->width = size.width; - } else if (sizeMeasureMode & RCTSurfaceSizeMeasureModeWidthAtMost) { + } else if ((sizeMeasureMode & RCTSurfaceSizeMeasureModeWidthAtMost) != 0) { maximumSize->width = size.width; } - if (sizeMeasureMode & RCTSurfaceSizeMeasureModeHeightExact) { + if ((sizeMeasureMode & RCTSurfaceSizeMeasureModeHeightExact) != 0) { minimumSize->height = size.height; maximumSize->height = size.height; - } else if (sizeMeasureMode & RCTSurfaceSizeMeasureModeHeightAtMost) { + } else if ((sizeMeasureMode & RCTSurfaceSizeMeasureModeHeightAtMost) != 0) { maximumSize->height = size.height; } } diff --git a/packages/react-native/React/CoreModules/RCTActionSheetManager.mm b/packages/react-native/React/CoreModules/RCTActionSheetManager.mm index 658ec6b8b81e..58e2b66f1178 100644 --- a/packages/react-native/React/CoreModules/RCTActionSheetManager.mm +++ b/packages/react-native/React/CoreModules/RCTActionSheetManager.mm @@ -31,7 +31,7 @@ @implementation RCTActionSheetManager - (instancetype)init { self = [super init]; - if (self) { + if (self != nullptr) { _alertControllers = [NSMutableArray new]; } return self; @@ -53,7 +53,7 @@ - (void)presentViewController:(UIViewController *)alertController alertController.modalPresentationStyle = UIModalPresentationPopover; UIView *sourceView = parentViewController.view; - if (anchorViewTag) { + if (anchorViewTag != nullptr) { sourceView = [self.viewRegistry_DEPRECATED viewForReactTag:anchorViewTag]; } else { alertController.popoverPresentationController.permittedArrowDirections = 0; @@ -166,12 +166,12 @@ - (void)presentViewController:(UIViewController *)alertController index++; } - if (disabledButtonIndices) { + if (disabledButtonIndices != nullptr) { for (NSNumber *disabledButtonIndex in disabledButtonIndices) { if ([disabledButtonIndex integerValue] < buttons.count) { UIAlertAction *action = alertController.actions[[disabledButtonIndex integerValue]]; [action setEnabled:false]; - if (disabledButtonTintColor) { + if (disabledButtonTintColor != nullptr) { [action setValue:disabledButtonTintColor forKey:@"titleTextColor"]; } } else { @@ -235,14 +235,14 @@ - (void)presentViewController:(UIViewController *)alertController UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil]; dispatch_async(dispatch_get_main_queue(), ^{ - if (message) { + if (message != nullptr) { [items addObject:message]; } - if (URL) { + if (URL != nullptr) { if ([URL.scheme.lowercaseString isEqualToString:@"data"]) { NSError *error; NSData *data = [NSData dataWithContentsOfURL:URL options:(NSDataReadingOptions)0 error:&error]; - if (!data) { + if (data == nullptr) { failureCallback(@[ RCTJSErrorFromNSError(error) ]); return; } @@ -258,17 +258,17 @@ - (void)presentViewController:(UIViewController *)alertController UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; - if (subject) { + if (subject != nullptr) { [shareController setValue:subject forKey:@"subject"]; } - if (excludedActivityTypes) { + if (excludedActivityTypes != nullptr) { shareController.excludedActivityTypes = excludedActivityTypes; } UIViewController *controller = RCTPresentedViewController(); shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) { - if (activityError) { + if (activityError != nullptr) { failureCallback(@[ RCTJSErrorFromNSError(activityError) ]); } else if (completed || activityType == nil) { successCallback(@[ @(completed), RCTNullIfNil(activityType) ]); diff --git a/packages/react-native/React/CoreModules/RCTAlertController.mm b/packages/react-native/React/CoreModules/RCTAlertController.mm index 3131ff4db592..b275ebc5c67e 100644 --- a/packages/react-native/React/CoreModules/RCTAlertController.mm +++ b/packages/react-native/React/CoreModules/RCTAlertController.mm @@ -27,7 +27,7 @@ - (UIWindow *)alertWindow _alertWindow = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; } - if (_alertWindow) { + if (_alertWindow != nullptr) { _alertWindow.rootViewController = [UIViewController new]; _alertWindow.windowLevel = UIWindowLevelAlert + 1; } @@ -41,7 +41,7 @@ - (void)show:(BOOL)animated completion:(void (^)(void))completion UIUserInterfaceStyle style = self.overrideUserInterfaceStyle; if (style == UIUserInterfaceStyleUnspecified) { UIUserInterfaceStyle overriddenStyle = RCTKeyWindow().overrideUserInterfaceStyle; - style = overriddenStyle ? overriddenStyle : UIUserInterfaceStyleUnspecified; + style = (overriddenStyle != 0) ? overriddenStyle : UIUserInterfaceStyleUnspecified; } self.overrideUserInterfaceStyle = style; diff --git a/packages/react-native/React/CoreModules/RCTAlertManager.mm b/packages/react-native/React/CoreModules/RCTAlertManager.mm index 415bb1f6fb83..2b9fc60c9681 100644 --- a/packages/react-native/React/CoreModules/RCTAlertManager.mm +++ b/packages/react-native/React/CoreModules/RCTAlertManager.mm @@ -86,7 +86,7 @@ - (void)invalidate UIKeyboardType keyboardType = [RCTConvert UIKeyboardType:args.keyboardType()]; UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:args.userInterfaceStyle()]; - if (!title && !message) { + if ((title == nullptr) && (message == nullptr)) { RCTLogError(@"Must specify either an alert title, or message, or both"); return; } @@ -193,7 +193,7 @@ - (void)invalidate } } - if (!self->_alertControllers) { + if (self->_alertControllers == nullptr) { self->_alertControllers = [NSHashTable weakObjectsHashTable]; } [self->_alertControllers addObject:alertController]; diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.mm b/packages/react-native/React/CoreModules/RCTDevMenu.mm index 4a201fc1730e..7282400cb3b3 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.mm +++ b/packages/react-native/React/CoreModules/RCTDevMenu.mm @@ -47,7 +47,7 @@ @implementation RCTDevMenuItem { - (instancetype)initWithTitleBlock:(RCTDevMenuItemTitleBlock)titleBlock handler:(dispatch_block_t)handler { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _titleBlock = [titleBlock copy]; _handler = [handler copy]; } @@ -72,14 +72,14 @@ + (instancetype)buttonItemWithTitle:(NSString *)title handler:(dispatch_block_t) - (void)callHandler { - if (_handler) { + if (_handler != nullptr) { _handler(); } } - (NSString *)title { - if (_titleBlock) { + if (_titleBlock != nullptr) { return _titleBlock(); } return nil; @@ -120,7 +120,7 @@ + (BOOL)requiresMainQueueSetup - (instancetype)init { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showOnShake) name:RCTShowDevMenuNotification @@ -214,7 +214,7 @@ - (void)toggle if (_actionSheet.isBeingPresented || _actionSheet.beingDismissed) { return; } - if (_actionSheet) { + if (_actionSheet != nullptr) { [_actionSheet dismissViewControllerAnimated:YES completion:^(void) { self->_actionSheet = nil; @@ -379,7 +379,7 @@ - (void)setDefaultJSBundle RCT_EXPORT_METHOD(show) { - if (_actionSheet || RCTRunningInAppExtension()) { + if ((_actionSheet != nullptr) || RCTRunningInAppExtension()) { return; } @@ -412,7 +412,7 @@ - (void)setDefaultJSBundle - (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__nullable)item { return ^(__unused UIAlertAction *action) { - if (item) { + if (item != nullptr) { [item callHandler]; } diff --git a/packages/react-native/React/CoreModules/RCTEventDispatcher.mm b/packages/react-native/React/CoreModules/RCTEventDispatcher.mm index dbb4e4b21116..770c746c0bf5 100644 --- a/packages/react-native/React/CoreModules/RCTEventDispatcher.mm +++ b/packages/react-native/React/CoreModules/RCTEventDispatcher.mm @@ -71,12 +71,14 @@ - (void)sendAppEventWithName:(NSString *)name body:(id)body { [_callableJSModules invokeModule:@"RCTNativeAppEventEmitter" method:@"emit" - withArgs:body ? @[ name, body ] : @[ name ]]; + withArgs:(body != nullptr) ? @[ name, body ] : @[ name ]]; } - (void)sendDeviceEventWithName:(NSString *)name body:(id)body { - [_callableJSModules invokeModule:@"RCTDeviceEventEmitter" method:@"emit" withArgs:body ? @[ name, body ] : @[ name ]]; + [_callableJSModules invokeModule:@"RCTDeviceEventEmitter" + method:@"emit" + withArgs:(body != nullptr) ? @[ name, body ] : @[ name ]]; } - (void)sendTextEventWithType:(RCTTextEventType)type @@ -91,13 +93,13 @@ - (void)sendTextEventWithType:(RCTTextEventType)type @"eventCount" : @(eventCount), }]; - if (text) { + if (text != nullptr) { // We copy the string here because if it's a mutable string it may get released before we dispatch the event on a // different thread, causing a crash. body[@"text"] = [text copy]; } - if (key) { + if (key != nullptr) { if (key.length == 0) { key = @"Backspace"; // backspace } else { @@ -142,7 +144,7 @@ - (void)sendEvent:(id)event if (event.canCoalesce) { eventID = RCTGetEventID(event.viewTag, event.eventName, event.coalescingKey); id previousEvent = _events[eventID]; - if (previousEvent) { + if (previousEvent != nullptr) { event = [previousEvent coalesceWithEvent:event]; } else { [_eventQueue addObject:eventID]; @@ -173,13 +175,13 @@ - (void)sendEvent:(id)event [_eventQueueLock unlock]; if (scheduleEventsDispatch) { - if (_bridge) { + if (_bridge != nullptr) { [_bridge dispatchBlock:^{ [self flushEventsQueue]; } queue:RCTJSThread]; - } else if (_dispatchToJSThread) { + } else if (_dispatchToJSThread != nullptr) { _dispatchToJSThread(^{ [self flushEventsQueue]; }); @@ -236,7 +238,7 @@ - (void)_notifyEventDispatcherObserversOfEvent_DEPRECATED:(NSNotification *)noti { NSDictionary *userInfo = notification.userInfo; id event = [userInfo objectForKey:@"event"]; - if (event) { + if (event != nullptr) { [self notifyObserversOfEvent:event]; } } diff --git a/packages/react-native/React/CoreModules/RCTExceptionsManager.mm b/packages/react-native/React/CoreModules/RCTExceptionsManager.mm index 2a6f3a5c461a..dea805fddb25 100644 --- a/packages/react-native/React/CoreModules/RCTExceptionsManager.mm +++ b/packages/react-native/React/CoreModules/RCTExceptionsManager.mm @@ -30,7 +30,7 @@ @implementation RCTExceptionsManager - (instancetype)initWithDelegate:(id)delegate { - if ((self = [self init])) { + if ((self = [self init]) != nullptr) { _delegate = delegate; } return self; @@ -46,7 +46,7 @@ - (void)reportSoft:(NSString *)message [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; } - if (_delegate) { + if (_delegate != nullptr) { [_delegate handleSoftJSExceptionWithMessage:message stack:stack exceptionId:[NSNumber numberWithDouble:exceptionId] @@ -64,7 +64,7 @@ - (void)reportFatal:(NSString *)message [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; } - if (_delegate) { + if (_delegate != nullptr) { [_delegate handleFatalJSExceptionWithMessage:message stack:stack exceptionId:[NSNumber numberWithDouble:exceptionId] @@ -107,13 +107,13 @@ - (void)reportFatal:(NSString *)message { NSMutableDictionary *mutableErrorData = [NSMutableDictionary new]; mutableErrorData[@"message"] = data.message(); - if (data.originalMessage()) { + if (data.originalMessage() != nullptr) { mutableErrorData[@"originalMessage"] = data.originalMessage(); } - if (data.name()) { + if (data.name() != nullptr) { mutableErrorData[@"name"] = data.name(); } - if (data.componentStack()) { + if (data.componentStack() != nullptr) { mutableErrorData[@"componentStack"] = data.componentStack(); } @@ -141,7 +141,7 @@ - (void)reportFatal:(NSString *)message mutableErrorData[@"id"] = @(data.id_()); mutableErrorData[@"isFatal"] = @(data.isFatal()); - if (data.extraData()) { + if (data.extraData() != nullptr) { mutableErrorData[@"extraData"] = data.extraData(); } diff --git a/packages/react-native/React/CoreModules/RCTFPSGraph.mm b/packages/react-native/React/CoreModules/RCTFPSGraph.mm index 1fc4bd24b9c3..97333667c4f5 100644 --- a/packages/react-native/React/CoreModules/RCTFPSGraph.mm +++ b/packages/react-native/React/CoreModules/RCTFPSGraph.mm @@ -37,7 +37,7 @@ @implementation RCTFPSGraph { - (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color { - if ((self = [super initWithFrame:frame])) { + if ((self = [super initWithFrame:frame]) != nullptr) { _frameCount = -1; _prevTime = -1; _maxFPS = 0; @@ -64,7 +64,7 @@ - (void)dealloc - (CAShapeLayer *)graph { - if (!_graph) { + if (_graph == nullptr) { _graph = [CAShapeLayer new]; _graph.frame = self.bounds; _graph.backgroundColor = [_color colorWithAlphaComponent:0.2].CGColor; @@ -76,7 +76,7 @@ - (CAShapeLayer *)graph - (UILabel *)label { - if (!_label) { + if (_label == nullptr) { _label = [[UILabel alloc] initWithFrame:self.bounds]; _label.font = [UIFont boldSystemFontOfSize:13]; _label.textAlignment = NSTextAlignmentCenter; diff --git a/packages/react-native/React/CoreModules/RCTLogBox.mm b/packages/react-native/React/CoreModules/RCTLogBox.mm index 70e48175cb3d..c0b7448cda87 100644 --- a/packages/react-native/React/CoreModules/RCTLogBox.mm +++ b/packages/react-native/React/CoreModules/RCTLogBox.mm @@ -45,23 +45,23 @@ - (void)setSurfacePresenter:(id)surfacePresenter __weak RCTLogBox *weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ __strong RCTLogBox *strongSelf = weakSelf; - if (!strongSelf) { + if (strongSelf == nullptr) { return; } - if (strongSelf->_view) { + if (strongSelf->_view != nullptr) { [strongSelf->_view show]; return; } - if (strongSelf->_bridgelessSurfacePresenter) { + if (strongSelf->_bridgelessSurfacePresenter != nullptr) { strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() surfacePresenter:strongSelf->_bridgelessSurfacePresenter]; [strongSelf->_view show]; } #ifndef RCT_FIT_RM_OLD_RUNTIME - else if (strongSelf->_bridge && strongSelf->_bridge.valid) { - if (strongSelf->_bridge.surfacePresenter) { + else if ((strongSelf->_bridge != nullptr) && strongSelf->_bridge.valid) { + if (strongSelf->_bridge.surfacePresenter != nullptr) { strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() surfacePresenter:strongSelf->_bridge.surfacePresenter]; } else { @@ -80,7 +80,7 @@ - (void)setSurfacePresenter:(id)surfacePresenter __weak RCTLogBox *weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ __strong RCTLogBox *strongSelf = weakSelf; - if (!strongSelf) { + if (strongSelf == nullptr) { return; } [strongSelf->_view setHidden:YES]; diff --git a/packages/react-native/React/CoreModules/RCTLogBoxView.mm b/packages/react-native/React/CoreModules/RCTLogBoxView.mm index fc0f24809eab..e1073a6b1d2c 100644 --- a/packages/react-native/React/CoreModules/RCTLogBoxView.mm +++ b/packages/react-native/React/CoreModules/RCTLogBoxView.mm @@ -19,7 +19,7 @@ @implementation RCTLogBoxView { - (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { + if ((self = [super initWithFrame:frame]) != nullptr) { self.windowLevel = UIWindowLevelStatusBar - 1; self.backgroundColor = [UIColor clearColor]; } diff --git a/packages/react-native/React/CoreModules/RCTPerfMonitor.mm b/packages/react-native/React/CoreModules/RCTPerfMonitor.mm index 86eabc6fb20a..e67ef6a40764 100644 --- a/packages/react-native/React/CoreModules/RCTPerfMonitor.mm +++ b/packages/react-native/React/CoreModules/RCTPerfMonitor.mm @@ -134,7 +134,7 @@ - (void)invalidate #if __has_include() - (RCTDevMenuItem *)devMenuItem { - if (!_devMenuItem) { + if (_devMenuItem == nullptr) { __weak __typeof__(self) weakSelf = self; __weak RCTDevSettings *devSettings = [self->_moduleRegistry moduleForName:"DevSettings"]; if (devSettings.isPerfMonitorShown) { @@ -161,7 +161,7 @@ - (RCTDevMenuItem *)devMenuItem - (UIPanGestureRecognizer *)gestureRecognizer { - if (!_gestureRecognizer) { + if (_gestureRecognizer == nullptr) { _gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gesture:)]; } @@ -170,7 +170,7 @@ - (UIPanGestureRecognizer *)gestureRecognizer - (UIView *)container { - if (!_container) { + if (_container == nullptr) { UIEdgeInsets safeInsets = RCTKeyWindow().safeAreaInsets; _container = @@ -188,7 +188,7 @@ - (UIView *)container - (UILabel *)memory { - if (!_memory) { + if (_memory == nullptr) { _memory = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 44, RCTPerfMonitorBarHeight)]; _memory.font = [UIFont systemFontOfSize:12]; _memory.numberOfLines = 3; @@ -200,7 +200,7 @@ - (UILabel *)memory - (UILabel *)heap { - if (!_heap) { + if (_heap == nullptr) { _heap = [[UILabel alloc] initWithFrame:CGRectMake(44, 0, 44, RCTPerfMonitorBarHeight)]; _heap.font = [UIFont systemFontOfSize:12]; _heap.numberOfLines = 3; @@ -212,7 +212,7 @@ - (UILabel *)heap - (UILabel *)views { - if (!_views) { + if (_views == nullptr) { _views = [[UILabel alloc] initWithFrame:CGRectMake(88, 0, 44, RCTPerfMonitorBarHeight)]; _views.font = [UIFont systemFontOfSize:12]; _views.numberOfLines = 3; @@ -224,7 +224,7 @@ - (UILabel *)views - (RCTFPSGraph *)uiGraph { - if (!_uiGraph) { + if (_uiGraph == nullptr) { _uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(134, 14, 40, 30) color:[UIColor lightGrayColor]]; } return _uiGraph; @@ -232,7 +232,7 @@ - (RCTFPSGraph *)uiGraph - (RCTFPSGraph *)jsGraph { - if (!_jsGraph) { + if (_jsGraph == nullptr) { _jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(178, 14, 40, 30) color:[UIColor lightGrayColor]]; } return _jsGraph; @@ -240,7 +240,7 @@ - (RCTFPSGraph *)jsGraph - (UILabel *)uiGraphLabel { - if (!_uiGraphLabel) { + if (_uiGraphLabel == nullptr) { _uiGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(134, 3, 40, 10)]; _uiGraphLabel.font = [UIFont systemFontOfSize:11]; _uiGraphLabel.textAlignment = NSTextAlignmentCenter; @@ -252,7 +252,7 @@ - (UILabel *)uiGraphLabel - (UILabel *)jsGraphLabel { - if (!_jsGraphLabel) { + if (_jsGraphLabel == nullptr) { _jsGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(178, 3, 38, 10)]; _jsGraphLabel.font = [UIFont systemFontOfSize:11]; _jsGraphLabel.textAlignment = NSTextAlignmentCenter; @@ -264,7 +264,7 @@ - (UILabel *)jsGraphLabel - (UITableView *)metrics { - if (!_metrics) { + if (_metrics == nullptr) { _metrics = [[UITableView alloc] initWithFrame:CGRectMake( 0, RCTPerfMonitorBarHeight, @@ -281,7 +281,7 @@ - (UITableView *)metrics - (void)show { - if (_container) { + if (_container != nullptr) { return; } @@ -317,7 +317,7 @@ - (void)show - (void)hide { - if (!_container) { + if (_container == nullptr) { return; } @@ -360,7 +360,7 @@ - (void)redirectLogs dispatch_io_set_low_water(_io, 20); dispatch_io_read(_io, 0, SIZE_MAX, _queue, ^(__unused bool done, dispatch_data_t data, __unused int error) { - if (!data) { + if (data == nullptr) { return; } @@ -391,7 +391,7 @@ - (void)parse:(NSString *)log GCRegex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; }); - if (_remaining) { + if (_remaining != nullptr) { log = [_remaining stringByAppendingString:log]; _remaining = nil; } @@ -404,7 +404,7 @@ - (void)parse:(NSString *)log for (NSString *line in lines) { NSTextCheckingResult *match = [GCRegex firstMatchInString:line options:0 range:NSMakeRange(0, line.length)]; - if (match) { + if (match != nullptr) { NSString *heapSizeStr = [line substringWithRange:[match rangeAtIndex:2]]; _heapSize = [heapSizeStr integerValue]; } @@ -417,7 +417,7 @@ - (void)updateStats NSUInteger viewCount = views.count; NSUInteger visibleViewCount = 0; for (UIView *view in views.allValues) { - if (view.window || view.superview.window) { + if ((view.window != nullptr) || (view.superview.window != nullptr)) { visibleViewCount++; } } @@ -436,7 +436,7 @@ - (void)updateStats __weak __typeof__(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ __strong __typeof__(weakSelf) strongSelf = weakSelf; - if (strongSelf && strongSelf->_container.superview) { + if ((strongSelf != nullptr) && (strongSelf->_container.superview != nullptr)) { [strongSelf updateStats]; } }); @@ -512,7 +512,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:RCTPerfMonitorCellIdentifier forIndexPath:indexPath]; - if (!cell) { + if (cell == nullptr) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RCTPerfMonitorCellIdentifier]; } From f9cecc5f00f0a6008d32ad2ff2e8d3cc654e9c58 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Wed, 3 Sep 2025 10:11:41 -0700 Subject: [PATCH 0036/1110] fix: correctly assign name to both begin and end events for measures (#53588) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53588 # Changelog: [Internal] Since the `name` was already moved for the begin event, there is nothing to be moved for `end` event. Instead, we will be creating a copy for the `begin` event. This was actually affecting some entries on a timeline, like component triggers (yellow ones). Reviewed By: vzaidman Differential Revision: D81589847 fbshipit-source-id: 3b7d801d3429217ce279ed7de41c40c3838a5f37 --- .../jsinspector-modern/tracing/PerformanceTracer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index 238003fd3931..80db2a1dce12 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -445,7 +445,7 @@ void PerformanceTracer::enqueueTraceEventsFromPerformanceTracerEvent( events.emplace_back(TraceEvent{ .id = eventId, - .name = std::move(event.name), + .name = event.name, .cat = "blink.user_timing", .ph = 'b', .ts = event.start, From 9a95e19b36377a83cead41836f953371e3236c33 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 3 Sep 2025 13:51:37 -0700 Subject: [PATCH 0037/1110] Simplify BridgelessReactStateTracker (#53577) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53577 Simplify the API to keep all formatting inside of ReactHostStateTracker and remove the `bridgeless` part of the name. Bit more efficient binary-size wise. Changelog: [Internal] Reviewed By: alanleedev Differential Revision: D81445833 fbshipit-source-id: 5bc8bc9e3de326f23e95e01e889b4e2806438c06 --- .../facebook/react/runtime/ReactHostImpl.kt | 167 ++++++++---------- ...ateTracker.kt => ReactHostStateTracker.kt} | 13 +- .../runtime/ReactLifecycleStateManager.kt | 20 +-- 3 files changed, 91 insertions(+), 109 deletions(-) rename packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/{BridgelessReactStateTracker.kt => ReactHostStateTracker.kt} (54%) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt index 4a7f9a7c95c7..258a87914625 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt @@ -128,12 +128,12 @@ public class ReactHostImpl( private var reactInstance: ReactInstance? = null private val bridgelessReactContextRef = BridgelessAtomicRef() + private val id = counter.getAndIncrement() private val activity = AtomicReference() private val lastUsedActivityRef = AtomicReference(WeakReference(null)) - private val bridgelessReactStateTracker = BridgelessReactStateTracker(ReactBuildConfig.DEBUG) - private val reactLifecycleStateManager = ReactLifecycleStateManager(bridgelessReactStateTracker) - private val id = counter.getAndIncrement() + private val stateTracker = ReactHostStateTracker(id) + private val reactLifecycleStateManager = ReactLifecycleStateManager(stateTracker) private var memoryPressureListener: MemoryPressureListener? = null private var defaultHardwareBackBtnHandler: DefaultHardwareBackBtnHandler? = null @@ -178,11 +178,11 @@ public class ReactHostImpl( /** Initialize and run a React Native surface in a background without mounting real views. */ internal fun prerenderSurface(surface: ReactSurfaceImpl): TaskInterface { val method = "prerenderSurface(surfaceId = ${surface.surfaceID})" - log(method, "Schedule") + stateTracker.enterState(method, "Schedule") attachSurface(surface) return callAfterGetOrCreateReactInstance(method, bgExecutor) { reactInstance: ReactInstance -> - log(method, "Execute") + stateTracker.enterState(method, "Execute") reactInstance.prerenderSurface(surface) } } @@ -195,11 +195,11 @@ public class ReactHostImpl( */ internal fun startSurface(surface: ReactSurfaceImpl): TaskInterface { val method = "startSurface(surfaceId = ${surface.surfaceID})" - log(method, "Schedule") + stateTracker.enterState(method, "Schedule") attachSurface(surface) return callAfterGetOrCreateReactInstance(method, bgExecutor) { reactInstance: ReactInstance -> - log(method, "Execute") + stateTracker.enterState(method, "Execute") reactInstance.startSurface(surface) } } @@ -212,11 +212,11 @@ public class ReactHostImpl( */ internal fun stopSurface(surface: ReactSurfaceImpl): TaskInterface { val method = "stopSurface(surfaceId = ${surface.surfaceID})" - log(method, "Schedule") + stateTracker.enterState(method, "Schedule") detachSurface(surface) return callWithExistingReactInstance(method, bgExecutor) { reactInstance: ReactInstance -> - log(method, "Execute") + stateTracker.enterState(method, "Execute") reactInstance.stopSurface(surface) } .makeVoid() @@ -238,8 +238,7 @@ public class ReactHostImpl( @ThreadConfined(ThreadConfined.UI) override fun onHostResume(activity: Activity?) { - val method = "onHostResume(activity)" - log(method) + stateTracker.enterState("onHostResume(activity)") currentActivity = activity @@ -249,8 +248,7 @@ public class ReactHostImpl( @ThreadConfined(ThreadConfined.UI) override fun onHostLeaveHint(activity: Activity?) { - val method = "onUserLeaveHint(activity)" - log(method) + stateTracker.enterState("onUserLeaveHint(activity)") currentReactContext?.onUserLeaveHint(activity) } @@ -258,7 +256,7 @@ public class ReactHostImpl( @ThreadConfined(ThreadConfined.UI) override fun onHostPause(activity: Activity?) { val method = "onHostPause(activity)" - log(method) + stateTracker.enterState(method) val currentActivity = this.currentActivity if (currentActivity != null) { @@ -269,7 +267,7 @@ public class ReactHostImpl( val isNotSameActivityMessage = "Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass" if (ReactNativeFeatureFlags.skipActivityIdentityAssertionOnHostPause()) { - log(method, isNotSameActivityMessage) + FLog.w(TAG, method, isNotSameActivityMessage) } else { Assertions.assertCondition(isSameActivity, isNotSameActivityMessage) } @@ -284,8 +282,7 @@ public class ReactHostImpl( /** To be called when the host activity is paused. */ @ThreadConfined(ThreadConfined.UI) override fun onHostPause() { - val method = "onHostPause()" - log(method) + stateTracker.enterState("onHostPause()") maybeEnableDevSupport(false) defaultHardwareBackBtnHandler = null @@ -295,8 +292,7 @@ public class ReactHostImpl( /** To be called when the host activity is destroyed. */ @ThreadConfined(ThreadConfined.UI) override fun onHostDestroy() { - val method = "onHostDestroy()" - log(method) + stateTracker.enterState("onHostDestroy()") maybeEnableDevSupport(false) moveToHostDestroy(currentReactContext) @@ -304,8 +300,7 @@ public class ReactHostImpl( @ThreadConfined(ThreadConfined.UI) override fun onHostDestroy(activity: Activity?) { - val method = "onHostDestroy(activity)" - log(method) + stateTracker.enterState("onHostDestroy(activity)") val currentActivity = this.currentActivity @@ -383,7 +378,7 @@ public class ReactHostImpl( { val reloadTask = (destroyTask?.let { destroyTask -> - log( + stateTracker.enterState( "reload()", "Waiting for destroy to finish, before reloading React Native.", ) @@ -485,7 +480,7 @@ public class ReactHostImpl( { val reloadTask = reloadTask if (reloadTask != null) { - log( + stateTracker.enterState( "destroy()", "Reloading React Native. Waiting for reload to finish before destroying React Native.", ) @@ -671,20 +666,20 @@ public class ReactHostImpl( internal fun loadBundle(bundleLoader: JSBundleLoader): Task { val method = "loadBundle()" - log(method, "Schedule") + stateTracker.enterState(method, "Schedule") return callWithExistingReactInstance(method) { reactInstance: ReactInstance -> - log(method, "Execute") + stateTracker.enterState(method, "Execute") reactInstance.loadJSBundle(bundleLoader) } } internal fun registerSegment(segmentId: Int, path: String, callback: Callback?): Task { val method = "registerSegment(segmentId = \"$segmentId\", path = \"$path\")" - log(method, "Schedule") + stateTracker.enterState(method, "Schedule") return callWithExistingReactInstance(method) { reactInstance: ReactInstance -> - log(method, "Execute") + stateTracker.enterState(method, "Execute") reactInstance.registerSegment(segmentId, path) checkNotNull(callback).invoke() } @@ -692,7 +687,7 @@ public class ReactHostImpl( internal fun handleHostException(e: Exception) { val method = "handleHostException(message = \"${e.message}\")" - log(method) + stateTracker.enterState(method) if (useDevSupport) { devSupportManager.handleException(e) @@ -722,12 +717,12 @@ public class ReactHostImpl( } internal fun attachSurface(surface: ReactSurfaceImpl) { - log("attachSurface(surfaceId = ${surface.surfaceID})") + stateTracker.enterState("attachSurface(surfaceId = ${surface.surfaceID})") synchronized(attachedSurfaces) { attachedSurfaces.add(surface) } } internal fun detachSurface(surface: ReactSurfaceImpl) { - log("detachSurface(surfaceId = ${surface.surfaceID})") + stateTracker.enterState("detachSurface(surfaceId = ${surface.surfaceID})") synchronized(attachedSurfaces) { attachedSurfaces.remove(surface) } } @@ -757,8 +752,7 @@ public class ReactHostImpl( return it } - val method = "getOrCreateStartTask()" - log(method, "Schedule") + stateTracker.enterState("getOrCreateStartTask()", "Schedule") if (ReactBuildConfig.DEBUG) { Assertions.assertCondition( ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture(), @@ -820,7 +814,7 @@ public class ReactHostImpl( throwable: Throwable? = null, ) { val method = "raiseSoftException($callingMethod)" - log(method, message) + stateTracker.enterState(method, message) ReactSoftExceptionLogger.logSoftException( TAG, ReactNoCrashSoftException("$method: $message", throwable), @@ -875,14 +869,6 @@ public class ReactHostImpl( executor, ) - private fun getOrCreateReactContext(): BridgelessReactContext { - val method = "getOrCreateReactContext()" - return bridgelessReactContextRef.getOrCreate { - log(method, "Creating BridgelessReactContext") - BridgelessReactContext(context, this) - } - } - /** * Entrypoint to create the ReactInstance. * @@ -903,14 +889,14 @@ public class ReactHostImpl( ): Task { val method = "waitThenCallGetOrCreateReactInstanceTaskWithRetries" reloadTask?.let { task -> - log(method, "React Native is reloading. Return reload task.") + stateTracker.enterState(method, "React Native is reloading. Return reload task.") return task } destroyTask?.let { task -> val shouldTryAgain = tryNum < maxTries if (shouldTryAgain) { - log( + stateTracker.enterState( method, "React Native is tearing down.Wait for teardown to finish, before trying again (try count = $tryNum).", ) @@ -938,10 +924,10 @@ public class ReactHostImpl( @ThreadConfined("ReactHost") private fun getOrCreateReactInstanceTask(): Task { val method = "getOrCreateReactInstanceTask()" - log(method) + stateTracker.enterState(method) return createReactInstanceTaskRef.getOrCreate { - log(method, "Start") + stateTracker.enterState(method, "Start") Assertions.assertCondition( !hostInvalidated, "Cannot start a new ReactInstance on an invalidated ReactHost", @@ -956,10 +942,14 @@ public class ReactHostImpl( jsBundleLoader.onSuccess( { task -> val bundleLoader = checkNotNull(task.getResult()) - val reactContext = getOrCreateReactContext() + val reactContext = + bridgelessReactContextRef.getOrCreate { + stateTracker.enterState(method, "Creating BridgelessReactContext") + BridgelessReactContext(context, this) + } reactContext.jsExceptionHandler = devSupportManager - log(method, "Creating ReactInstance") + stateTracker.enterState(method, "Creating ReactInstance") val instance = ReactInstance( reactContext, @@ -980,10 +970,13 @@ public class ReactHostImpl( // as TurboModuleManager will handle any concurrent access instance.initializeEagerTurboModules() - log(method, "Loading JS Bundle") + stateTracker.enterState(method, "Loading JS Bundle") instance.loadJSBundle(bundleLoader) - log(method, "Calling DevSupportManagerBase.onNewReactContextCreated(reactContext)") + stateTracker.enterState( + method, + "DevSupportManager.onNewReactContextCreated()", + ) devSupportManager.onNewReactContextCreated(reactContext) reactContext.runOnJSQueueThread { @@ -1040,7 +1033,7 @@ public class ReactHostImpl( reactLifecycleStateManager.resumeReactContextIfHostResumed(reactContext, currentActivity) } - log(method, "Executing ReactInstanceEventListeners") + stateTracker.enterState(method, "Executing ReactInstanceEventListeners") for (listener in reactInstanceEventListeners) { listener.onReactContextInitialized(reactContext) } @@ -1053,8 +1046,7 @@ public class ReactHostImpl( private val jsBundleLoader: Task get() { - val method = "getJSBundleLoader()" - log(method) + stateTracker.enterState("getJSBundleLoader()") if (useDevSupport && allowPackagerServerAccess) { return isMetroRunning.onSuccessTask( @@ -1093,13 +1085,13 @@ public class ReactHostImpl( private val isMetroRunning: Task get() { val method = "isMetroRunning()" - log(method) + stateTracker.enterState(method) val taskCompletionSource = TaskCompletionSource() val asyncDevSupportManager = devSupportManager asyncDevSupportManager.isPackagerRunning { packagerIsRunning: Boolean -> - log(method, "Async result = $packagerIsRunning") + stateTracker.enterState(method, "Async result = $packagerIsRunning") taskCompletionSource.setResult(packagerIsRunning) } @@ -1108,7 +1100,7 @@ public class ReactHostImpl( private fun loadJSBundleFromMetro(): Task { val method = "loadJSBundleFromMetro()" - log(method) + stateTracker.enterState(method) val taskCompletionSource = TaskCompletionSource() val asyncDevSupportManager = devSupportManager as DevSupportManagerBase @@ -1121,7 +1113,7 @@ public class ReactHostImpl( bundleURL, object : BundleLoadCallback { override fun onSuccess() { - log(method, "Creating BundleLoader") + stateTracker.enterState(method, "Creating BundleLoader") val bundleLoader = JSBundleLoader.createCachedBundleFromNetworkLoader( bundleURL, @@ -1139,16 +1131,8 @@ public class ReactHostImpl( return taskCompletionSource.task } - private fun log(method: String, message: String) { - bridgelessReactStateTracker.enterState("ReactHost{$id}.$method: $message") - } - - private fun log(method: String) { - bridgelessReactStateTracker.enterState("ReactHost{$id}.$method") - } - private fun stopAttachedSurfaces(method: String, reactInstance: ReactInstance) { - log(method, "Stopping all React Native surfaces") + stateTracker.enterState(method, "Stopping all React Native surfaces") synchronized(attachedSurfaces) { for (surface in attachedSurfaces) { reactInstance.stopSurface(surface) @@ -1158,7 +1142,7 @@ public class ReactHostImpl( } private fun startAttachedSurfaces(method: String, reactInstance: ReactInstance) { - log(method, "Restarting previously running React Native Surfaces") + stateTracker.enterState(method, "Restarting previously running React Native Surfaces") synchronized(attachedSurfaces) { for (surface in attachedSurfaces) { reactInstance.startSurface(surface) @@ -1225,7 +1209,7 @@ public class ReactHostImpl( @ThreadConfined("ReactHost") private fun getOrCreateReloadTask(reason: String): Task { val method = "getOrCreateReloadTask()" - log(method) + stateTracker.enterState(method) // Log how React Native is destroyed // TODO(T136397487): Remove after Venice is shipped to 100% @@ -1239,11 +1223,11 @@ public class ReactHostImpl( // When using the immediate executor, we want to avoid scheduling any further work immediately // when destruction is kicked off. - log(method, "Resetting createReactInstance task ref") + stateTracker.enterState(method, "Resetting createReactInstance task ref") return createReactInstanceTaskRef.andReset .continueWithTask( { task -> - log(method, "Starting React Native reload") + stateTracker.enterState(method, "Starting React Native reload") val reactInstance = taskUnwrapper(task, "1: Starting reload") unregisterInstanceFromInspector(reactInstance) @@ -1257,7 +1241,7 @@ public class ReactHostImpl( reactContext != null && reactLifecycleStateManager.lifecycleState == LifecycleState.RESUMED ) { - log(method, "Calling ReactContext.onHostPause()") + stateTracker.enterState(method, "Calling ReactContext.onHostPause()") reactContext.onHostPause() } Task.forResult(reactInstance) @@ -1284,21 +1268,24 @@ public class ReactHostImpl( } memoryPressureListener?.let { listener -> - log(method, "Removing memory pressure listener") + stateTracker.enterState(method, "Removing memory pressure listener") memoryPressureRouter.removeMemoryPressureListener(listener) } val reactContext = bridgelessReactContextRef.value if (reactContext != null) { - log(method, "Resetting ReactContext ref") + stateTracker.enterState(method, "Resetting ReactContext ref") bridgelessReactContextRef.reset() - log(method, "Destroying ReactContext") + stateTracker.enterState(method, "Destroying ReactContext") reactContext.destroy() } if (useDevSupport && reactContext != null) { - log(method, "Calling DevSupportManager.onReactInstanceDestroyed(reactContext)") + stateTracker.enterState( + method, + "Calling DevSupportManager.onReactInstanceDestroyed(reactContext)", + ) devSupportManager.onReactInstanceDestroyed(reactContext) } task @@ -1311,14 +1298,14 @@ public class ReactHostImpl( if (reactInstance == null) { raiseSoftException(method, "Skipping ReactInstance.destroy(): ReactInstance null") } else { - log(method, "Resetting ReactInstance ptr") + stateTracker.enterState(method, "Resetting ReactInstance ptr") this.reactInstance = null - log(method, "Destroying ReactInstance") + stateTracker.enterState(method, "Destroying ReactInstance") reactInstance.destroy() } - log(method, "Resetting start task ref") + stateTracker.enterState(method, "Resetting start task ref") startTask = null // Kickstart a new ReactInstance create @@ -1355,7 +1342,7 @@ public class ReactHostImpl( ) } - log(method, "Resetting reload task ref") + stateTracker.enterState(method, "Resetting reload task ref") reloadTask = null task }, @@ -1377,7 +1364,7 @@ public class ReactHostImpl( @ThreadConfined("ReactHost") private fun getOrCreateDestroyTask(reason: String, ex: Exception?): Task { val method = "getOrCreateDestroyTask()" - log(method) + stateTracker.enterState(method) // Log how React Native is destroyed // TODO(T136397487): Remove after Venice is shipped to 100% @@ -1391,11 +1378,11 @@ public class ReactHostImpl( // When using the immediate executor, we want to avoid scheduling any further work immediately // when destruction is kicked off. - log(method, "Resetting createReactInstance task ref") + stateTracker.enterState(method, "Resetting createReactInstance task ref") return createReactInstanceTaskRef.andReset .continueWithTask( { task: Task -> - log(method, "Starting React Native destruction") + stateTracker.enterState(method, "Starting React Native destruction") val reactInstance = taskUnwrapper(task, "1: Starting destroy") unregisterInstanceFromInspector(reactInstance) @@ -1410,7 +1397,7 @@ public class ReactHostImpl( // Step 1: Destroy DevSupportManager if (useDevSupport) { - log(method, "DevSupportManager cleanup") + stateTracker.enterState(method, "DevSupportManager cleanup") // TODO(T137233065): Disable DevSupportManager here devSupportManager.stopInspector() } @@ -1421,7 +1408,7 @@ public class ReactHostImpl( } // Step 2: Move React Native to onHostDestroy() - log(method, "Move ReactHost to onHostDestroy()") + stateTracker.enterState(method, "Move ReactHost to onHostDestroy()") reactLifecycleStateManager.moveToOnHostDestroy(reactContext) Task.forResult(reactInstance) }, @@ -1454,14 +1441,14 @@ public class ReactHostImpl( } // Step 4: De-register the memory pressure listener - log(method, "Destroying MemoryPressureRouter") + stateTracker.enterState(method, "Destroying MemoryPressureRouter") memoryPressureRouter.destroy(context) if (reactContext != null) { - log(method, "Resetting ReactContext ref") + stateTracker.enterState(method, "Resetting ReactContext ref") bridgelessReactContextRef.reset() - log(method, "Destroying ReactContext") + stateTracker.enterState(method, "Destroying ReactContext") reactContext.destroy() } @@ -1480,17 +1467,15 @@ public class ReactHostImpl( if (reactInstance == null) { raiseSoftException(method, "Skipping ReactInstance.destroy(): ReactInstance null") } else { - log(method, "Resetting ReactInstance ptr") + stateTracker.enterState(method, "Resetting ReactInstance ptr") this.reactInstance = null - log(method, "Destroying ReactInstance") + stateTracker.enterState(method, "Destroying ReactInstance") reactInstance.destroy() } - log(method, "Resetting start task ref") + stateTracker.enterState(method, "Resetting start/destroy task ref") startTask = null - - log(method, "Resetting destroy task ref") destroyTask = null task }, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactStateTracker.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostStateTracker.kt similarity index 54% rename from packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactStateTracker.kt rename to packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostStateTracker.kt index 5c6d401d4af1..2196b1b923ae 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactStateTracker.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostStateTracker.kt @@ -8,15 +8,14 @@ package com.facebook.react.runtime import com.facebook.common.logging.FLog -import java.util.Collections -internal class BridgelessReactStateTracker(private val shouldTrackStates: Boolean) { - private val states = Collections.synchronizedList(mutableListOf()) +internal class ReactHostStateTracker(private val id: Int) { - fun enterState(state: String) { - FLog.w(TAG, state) - if (shouldTrackStates) { - states.add(state) + fun enterState(method: String, message: String? = null) { + if (message == null) { + FLog.w(TAG, "ReactHost{%d}.%s", id, method) + } else { + FLog.w(TAG, "ReactHost{%d}.%s: %s", id, method, message) } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactLifecycleStateManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactLifecycleStateManager.kt index 51c0d95a7630..3a22e0a3fa56 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactLifecycleStateManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactLifecycleStateManager.kt @@ -13,9 +13,7 @@ import com.facebook.infer.annotation.ThreadConfined.UI import com.facebook.react.bridge.ReactContext import com.facebook.react.common.LifecycleState -internal class ReactLifecycleStateManager( - private val bridgelessReactStateTracker: BridgelessReactStateTracker -) { +internal class ReactLifecycleStateManager(private val stateTracker: ReactHostStateTracker) { private var state: LifecycleState = LifecycleState.BEFORE_CREATE val lifecycleState: LifecycleState @@ -24,7 +22,7 @@ internal class ReactLifecycleStateManager( @ThreadConfined(UI) fun resumeReactContextIfHostResumed(currentContext: ReactContext, activity: Activity?) { if (state == LifecycleState.RESUMED) { - bridgelessReactStateTracker.enterState("ReactContext.onHostResume()") + stateTracker.enterState("ReactContext.onHostResume()") currentContext.onHostResume(activity) } } @@ -36,7 +34,7 @@ internal class ReactLifecycleStateManager( } currentContext?.let { context -> - bridgelessReactStateTracker.enterState("ReactContext.onHostResume()") + stateTracker.enterState("ReactContext.onHostResume()") context.onHostResume(activity) } state = LifecycleState.RESUMED @@ -48,13 +46,13 @@ internal class ReactLifecycleStateManager( when (state) { LifecycleState.BEFORE_CREATE -> { // TODO: Investigate if we can remove this transition. - bridgelessReactStateTracker.enterState("ReactContext.onHostResume()") + stateTracker.enterState("ReactContext.onHostResume()") it.onHostResume(activity) - bridgelessReactStateTracker.enterState("ReactContext.onHostPause()") + stateTracker.enterState("ReactContext.onHostPause()") it.onHostPause() } LifecycleState.RESUMED -> { - bridgelessReactStateTracker.enterState("ReactContext.onHostPause()") + stateTracker.enterState("ReactContext.onHostPause()") it.onHostPause() } else -> { @@ -71,13 +69,13 @@ internal class ReactLifecycleStateManager( currentContext?.let { when (state) { LifecycleState.BEFORE_RESUME -> { - bridgelessReactStateTracker.enterState("ReactContext.onHostDestroy()") + stateTracker.enterState("ReactContext.onHostDestroy()") it.onHostDestroy() } LifecycleState.RESUMED -> { - bridgelessReactStateTracker.enterState("ReactContext.onHostPause()") + stateTracker.enterState("ReactContext.onHostPause()") it.onHostPause() - bridgelessReactStateTracker.enterState("ReactContext.onHostDestroy()") + stateTracker.enterState("ReactContext.onHostDestroy()") it.onHostDestroy() } else -> { From 43ad2c0abb7f3f028797bb80fdd6126d3f657169 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Wed, 3 Sep 2025 15:39:45 -0700 Subject: [PATCH 0038/1110] Remove contextContainer !=. nullptr check in ImageFetcher (#53574) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53574 Changelog: [Internal] This field is always non nullptr Reviewed By: javache Differential Revision: D81556283 fbshipit-source-id: d75b9cf9730f47c3d2d1ef028c2e738eda3dd785 --- .../renderer/imagemanager/ImageFetcher.cpp | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp index 01e2983223df..0152c4872e3e 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp @@ -16,26 +16,20 @@ namespace facebook::react { ImageFetcher::ImageFetcher( std::shared_ptr contextContainer) : contextContainer_(std::move(contextContainer)) { - if (contextContainer_ != nullptr) { - if (auto uiManagerCommitHookManager = - contextContainer_ - ->find>( - std::string(UIManagerCommitHookManagerKey)); - uiManagerCommitHookManager.has_value()) { - (*uiManagerCommitHookManager)->registerCommitHook(*this); - } + if (auto uiManagerCommitHookManager = + contextContainer_->find>( + std::string(UIManagerCommitHookManagerKey)); + uiManagerCommitHookManager.has_value()) { + (*uiManagerCommitHookManager)->registerCommitHook(*this); } } ImageFetcher::~ImageFetcher() { - if (contextContainer_ != nullptr) { - if (auto uiManagerCommitHookManager = - contextContainer_ - ->find>( - std::string(UIManagerCommitHookManagerKey)); - uiManagerCommitHookManager.has_value()) { - (*uiManagerCommitHookManager)->unregisterCommitHook(*this); - } + if (auto uiManagerCommitHookManager = + contextContainer_->find>( + std::string(UIManagerCommitHookManagerKey)); + uiManagerCommitHookManager.has_value()) { + (*uiManagerCommitHookManager)->unregisterCommitHook(*this); } } From 5d65794ee4fadc135d1c47ccd3bcce36e594ab8d Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Wed, 3 Sep 2025 19:01:15 -0700 Subject: [PATCH 0039/1110] Don't crash on reload (#53590) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53590 Changelog: [General][Fixed] ReactCxxPlatform] Don't crash on reload Reviewed By: shwanton Differential Revision: D81626640 fbshipit-source-id: 31016c67a1913a8be8578848e756e0447b802484 --- .../react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp index d35484e8455b..3b63b088cc26 100644 --- a/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp +++ b/packages/react-native/ReactCxxPlatform/react/runtime/ReactHost.cpp @@ -533,7 +533,9 @@ std::unordered_set ReactHost::getRunningSurfaces() const noexcept { void ReactHost::runOnScheduler( std::function&& task) const { - task(*scheduler_); + if (!isReloadingReactInstance_) { + task(*scheduler_); + } } void ReactHost::runOnRuntimeScheduler( From 48998b4c111fadeeaf68201105888847127af2ca Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 4 Sep 2025 03:06:33 -0700 Subject: [PATCH 0040/1110] Move IHermes to jsi/hermes.h (#53418) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53418 Expose these methods so that we can access from RN code. To minimize the change, a few methods that depend on other headers or preprocessor flags are wrapped into IHermesExtra in hermes/API/hermes.h. Changelog: [Internal] Reviewed By: tsaichien Differential Revision: D80740969 fbshipit-source-id: 79565d851bc1b0833931f4fe7fb62d89d3d669ef --- .../react-native/ReactCommon/jsi/jsi/hermes.h | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 packages/react-native/ReactCommon/jsi/jsi/hermes.h diff --git a/packages/react-native/ReactCommon/jsi/jsi/hermes.h b/packages/react-native/ReactCommon/jsi/jsi/hermes.h new file mode 100644 index 000000000000..04a7d924a7f7 --- /dev/null +++ b/packages/react-native/ReactCommon/jsi/jsi/hermes.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +struct SHUnit; +struct SHRuntime; +namespace hermes::vm { +class GCExecTrace; +} + +namespace facebook::hermes { + +namespace debugger { +class Debugger; +} + +/// Interface for Hermes-specific runtime methods.The actual implementations of +/// the pure virtual methods are provided by Hermes API. +class JSI_EXPORT IHermes : public jsi::ICast { + public: + static constexpr jsi::UUID uuid{ + 0xe85cfa22, + 0xdfae, + 0x11ef, + 0xa6f7, + 0x325096b39f47}; + + /// Return a ICast pointer to an object that be cast into the interface + /// IHermesRootAPI. This root API object has static lifetime. + virtual ICast* getHermesRootAPI() = 0; + + /// Serialize the sampled stack to the format expected by DevTools' + /// Profiler.stop return type. + virtual void sampledTraceToStreamInDevToolsFormat(std::ostream& stream) = 0; + + /// Resets the timezone offset cache used by Hermes for performance + /// optimization. Hermes maintains a cached timezone offset to accelerate date + /// and time calculations. However, this cache does not automatically detect + /// changes to the system timezone. When the system timezone changes, the + /// integration layer (e.g., React Native) must call this method to invalidate + /// the cache and ensure correct time calculations. + /// + /// \note Call this method immediately after detecting any timezone change in + /// the integrator. + virtual void resetTimezoneCache() = 0; + + /// Load a new segment into the Runtime. + /// The \param context must be a valid RequireContext retrieved from JS + /// using `require.context`. + virtual void loadSegment( + std::unique_ptr buffer, + const jsi::Value& context) = 0; + + /// Gets a guaranteed unique id for an Object (or, respectively, String + /// or PropNameId), which is assigned at allocation time and is + /// static throughout that object's (or string's, or PropNameID's) + /// lifetime. + virtual uint64_t getUniqueID(const jsi::Object& o) const = 0; + virtual uint64_t getUniqueID(const jsi::BigInt& s) const = 0; + virtual uint64_t getUniqueID(const jsi::String& s) const = 0; + virtual uint64_t getUniqueID(const jsi::PropNameID& pni) const = 0; + virtual uint64_t getUniqueID(const jsi::Symbol& sym) const = 0; + + /// Same as the other \c getUniqueID, except it can return 0 for some values. + /// 0 means there is no ID associated with the value. + virtual uint64_t getUniqueID(const jsi::Value& val) const = 0; + + /// From an ID retrieved from \p getUniqueID, go back to the object. + /// NOTE: This is much slower in general than the reverse operation, and takes + /// up more memory. Don't use this unless it's absolutely necessary. + /// \return a jsi::Object if a matching object is found, else returns null. + virtual jsi::Value getObjectForID(uint64_t id) = 0; + + /// Get a structure representing the execution history (currently just of + /// GC, but will be generalized as necessary), to aid in debugging + /// non-deterministic execution. + virtual const ::hermes::vm::GCExecTrace& getGCExecTrace() const = 0; + + /// Get IO tracking (aka HBC page access) info as a JSON string. + /// See hermes::vm::Runtime::getIOTrackingInfoJSON() for conditions + /// needed for there to be useful output. + virtual std::string getIOTrackingInfoJSON() = 0; + + /// \return a reference to the Debugger for this Runtime. + virtual debugger::Debugger& getDebugger() = 0; + + /// Register this runtime and thread for sampling profiler. Before using the + /// runtime on another thread, invoke this function again from the new thread + /// to make the sampling profiler target the new thread (and forget the old + /// thread). + virtual void registerForProfiling() = 0; + /// Unregister this runtime for sampling profiler. + virtual void unregisterForProfiling() = 0; + + /// Define methods to interrupt JS execution and set time limits. + /// All JS compiled to bytecode via prepareJS, or evaluateJS, will support + /// interruption and time limit monitoring if the runtime is configured with + /// AsyncBreakCheckInEval. If JS prepared in other ways is executed, care must + /// be taken to ensure that it is compiled in a mode that supports it (i.e., + /// the emitted code contains async break checks). + + /// Asynchronously terminates the current execution. This can be called on + /// any thread. + virtual void asyncTriggerTimeout() = 0; + + /// Register this runtime for execution time limit monitoring, with a time + /// limit of \p timeoutInMs milliseconds. + /// See compilation notes above. + virtual void watchTimeLimit(uint32_t timeoutInMs) = 0; + /// Unregister this runtime for execution time limit monitoring. + virtual void unwatchTimeLimit() = 0; + + /// Same as \c evaluate JavaScript but with a source map, which will be + /// applied to exception traces and debug information. + /// + /// This is an experimental Hermes-specific API. In the future it may be + /// renamed, moved or combined with another API, but the provided + /// functionality will continue to be available in some form. + virtual jsi::Value evaluateJavaScriptWithSourceMap( + const std::shared_ptr& buffer, + const std::shared_ptr& sourceMapBuf, + const std::string& sourceURL) = 0; + + /// Associate the SHUnit returned by \p shUnitCreator with this runtime and + /// run its initialization code. The unit will be freed when the runtime is + /// destroyed. + virtual jsi::Value evaluateSHUnit(SHUnit* (*shUnitCreator)()) = 0; + + /// Retrieve the underlying SHRuntime. + virtual SHRuntime* getSHRuntime() noexcept = 0; + + /// Returns the underlying low level Hermes VM runtime instance. + /// This function is considered unsafe and unstable. + /// Direct use of a vm::Runtime should be avoided as the lower level APIs are + /// unsafe and they can change without notice. + virtual void* getVMRuntimeUnsafe() const = 0; + + protected: + ~IHermes() = default; +}; +} // namespace facebook::hermes From 89d9533a97ef011ff9793da8d2d3cf32f0ed1e5e Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 4 Sep 2025 03:06:33 -0700 Subject: [PATCH 0041/1110] getSHUnitCreator() to IHermes (#53419) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53419 By default, this function returns nullptr. User can pass a preprocessor definition "-DHERMES_SH_UNIT_FN=sh_export_" (where is the name passed to shermesc when compiling the JS input), so that this function returns the function pointer, which can be passed to `evaluateSHUnit` for evaluation. Changelog: [Internal] Reviewed By: avp Differential Revision: D80747463 fbshipit-source-id: a798a7a572679444fca111c34674fd7ced9311f3 --- .../react-native/ReactCommon/jsi/jsi/hermes.h | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/jsi/jsi/hermes.h b/packages/react-native/ReactCommon/jsi/jsi/hermes.h index 04a7d924a7f7..640e51494a58 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/hermes.h +++ b/packages/react-native/ReactCommon/jsi/jsi/hermes.h @@ -11,6 +11,7 @@ struct SHUnit; struct SHRuntime; +using SHUnitCreator = SHUnit* (*)(); namespace hermes::vm { class GCExecTrace; } @@ -131,7 +132,7 @@ class JSI_EXPORT IHermes : public jsi::ICast { /// Associate the SHUnit returned by \p shUnitCreator with this runtime and /// run its initialization code. The unit will be freed when the runtime is /// destroyed. - virtual jsi::Value evaluateSHUnit(SHUnit* (*shUnitCreator)()) = 0; + virtual jsi::Value evaluateSHUnit(SHUnitCreator shUnitCreator) = 0; /// Retrieve the underlying SHRuntime. virtual SHRuntime* getSHRuntime() noexcept = 0; @@ -145,4 +146,23 @@ class JSI_EXPORT IHermes : public jsi::ICast { protected: ~IHermes() = default; }; + +/// Interface for provide Hermes backend specific methods. +class IHermesSHUnit : public jsi::ICast { + public: + static constexpr jsi::UUID uuid{ + 0x52a2d522, + 0xcbc6, + 0x4236, + 0x8d5d, + 0x2636c320ed65, + }; + + /// Get the unit creating function pointer which can be passed to + /// evaluateSHUnit() for evaluation. + virtual SHUnitCreator getSHUnitCreator() const = 0; + + protected: + ~IHermesSHUnit() = default; +}; } // namespace facebook::hermes From 8c9f366bdcfe0405532e957f3e378d4eb4fa1775 Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 4 Sep 2025 03:06:33 -0700 Subject: [PATCH 0042/1110] Move methods from IHermesExtra to IHermes (#53473) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53473 This is a cleanup of IHermesExtra: 1. Move dumpSampledTraceToProfile() and debugJavasScript() to IHermes. I'm still keeping the empty DebugFlags, since changing that requires more changes. It's also possible that we may need it in the future. 2. Remove `dumpBasicBlockProfileTrace`. Use writeBasicBlockProfileTraceToFile` if users need to dump the profile. Changelog: [Internal] Reviewed By: tsaichien Differential Revision: D81075460 fbshipit-source-id: b81005e531809cfd870fd9bdb5c0e17864ed92fb --- .../react-native/ReactCommon/jsi/jsi/hermes.h | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/react-native/ReactCommon/jsi/jsi/hermes.h b/packages/react-native/ReactCommon/jsi/jsi/hermes.h index 640e51494a58..364a645a9794 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/hermes.h +++ b/packages/react-native/ReactCommon/jsi/jsi/hermes.h @@ -18,6 +18,10 @@ class GCExecTrace; namespace facebook::hermes { +namespace sampling_profiler { +class Profile; +} + namespace debugger { class Debugger; } @@ -33,10 +37,27 @@ class JSI_EXPORT IHermes : public jsi::ICast { 0xa6f7, 0x325096b39f47}; + struct DebugFlags { + // Looking for the .lazy flag? It's no longer necessary. + // Source is evaluated lazily by default. See + // RuntimeConfig::CompilationMode. + }; + + /// Evaluate the given code in an unoptimized form, used for debugging. + /// This will be no-op if the implementation does not have debugger enabled. + virtual void debugJavaScript( + const std::string& src, + const std::string& sourceURL, + const DebugFlags& debugFlags) = 0; + /// Return a ICast pointer to an object that be cast into the interface /// IHermesRootAPI. This root API object has static lifetime. virtual ICast* getHermesRootAPI() = 0; + /// Dump sampled stack trace for a given runtime to a data structure that can + /// be used by third parties. + virtual sampling_profiler::Profile dumpSampledTraceToProfile() = 0; + /// Serialize the sampled stack to the format expected by DevTools' /// Profiler.stop return type. virtual void sampledTraceToStreamInDevToolsFormat(std::ostream& stream) = 0; From 863184fcf86a38a9955d9d43286b54970bdc34ef Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 4 Sep 2025 03:06:33 -0700 Subject: [PATCH 0043/1110] Move dumpOpcodeStats() to jsi::Instrumentation, remove IHermesExtra (#53475) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53475 This is cleanup of IHermesExtra. Move the last method in IHermesExtra, dumpOpcodeStats(), to jsi::Instrumentation, since other profile stats dumping methods live in that interface as well. Changelog: [Internal] Reviewed By: tsaichien Differential Revision: D81087047 fbshipit-source-id: e145aafea7459a161fca04ffc30f0838ee6c03c6 --- packages/react-native/ReactCommon/jsi/jsi/decorator.h | 4 ++++ packages/react-native/ReactCommon/jsi/jsi/instrumentation.h | 3 +++ packages/react-native/ReactCommon/jsi/jsi/jsi.cpp | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index 5aefd7d3c38d..c90c56468566 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -516,6 +516,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { .writeBasicBlockProfileTraceToFile(fileName); } + void dumpOpcodeStats(std::ostream& os) const override { + const_cast(plain()).instrumentation().dumpOpcodeStats(os); + } + /// Dump external profiler symbols to the given file name. void dumpProfilerSymbolsToFile(const std::string& fileName) const override { const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( diff --git a/packages/react-native/ReactCommon/jsi/jsi/instrumentation.h b/packages/react-native/ReactCommon/jsi/jsi/instrumentation.h index 726858ccde21..4a88951f6dfc 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/instrumentation.h +++ b/packages/react-native/ReactCommon/jsi/jsi/instrumentation.h @@ -121,6 +121,9 @@ class JSI_EXPORT Instrumentation { virtual void writeBasicBlockProfileTraceToFile( const std::string& fileName) const = 0; + /// Write the opcode stats to the given stream. + virtual void dumpOpcodeStats(std::ostream& os) const = 0; + /// Dump external profiler symbols to the given file name. virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; }; diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp index 571b41360a1d..79eb311dc3c5 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp @@ -334,6 +334,10 @@ Instrumentation& Runtime::instrumentation() { std::abort(); } + void dumpOpcodeStats(std::ostream&) const override { + std::abort(); + } + void dumpProfilerSymbolsToFile(const std::string&) const override { std::abort(); } From 4553f87489679664358a40d5ba302325acd4c4ff Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Thu, 4 Sep 2025 03:13:49 -0700 Subject: [PATCH 0044/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53591) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53591 Reviewed By: rshest Differential Revision: D81571883 fbshipit-source-id: 479a0764eabeac968028814ec6aafa32687b0905 --- .../graphics/RCTPlatformColorUtils.mm | 4 +- .../RCTImagePrimitivesConversions.h | 4 +- .../RCTAttributedTextUtils.h | 2 +- .../textlayoutmanager/RCTFontUtils.mm | 66 +++++++++---------- .../textlayoutmanager/RCTTextLayoutManager.mm | 8 +-- .../textlayoutmanager/TextLayoutManager.mm | 4 +- .../platform/ios/react/utils/FollyConvert.mm | 4 +- .../ios/RNTLegacyView.mm | 2 +- .../ios/RNTMyLegacyNativeViewManager.mm | 2 +- .../ios/RNTMyNativeViewManager.mm | 2 +- 10 files changed, 49 insertions(+), 49 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm index 4e79aa4dab44..ac83e924565e 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm @@ -153,11 +153,11 @@ : semanticString; NSDictionary *platformColorSelectorsDict = _PlatformColorSelectorsDict(); NSDictionary *colorInfo = platformColorSelectorsDict[platformColorString]; - if (colorInfo) { + if (colorInfo != nullptr) { SEL objcColorSelector = NSSelectorFromString([platformColorString stringByAppendingString:kColorSuffix]); if (![UIColor respondsToSelector:objcColorSelector]) { NSNumber *fallbackRGB = colorInfo[kFallbackARGBKey]; - if (fallbackRGB) { + if (fallbackRGB != nullptr) { return _UIColorFromHexValue(fallbackRGB); } } else { diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h index dcfe6b2a3af0..905cfe8f2692 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h @@ -81,7 +81,7 @@ inline static NSURL *NSURLFromImageSource(const facebook::react::ImageSource &im NSURL *url = [[NSURL alloc] initWithString:urlString]; - if (url.scheme) { + if (url.scheme != nullptr) { // Well-formed absolute URL. return url; } @@ -119,7 +119,7 @@ inline static NSURLRequest *NSURLRequestFromImageSource(const facebook::react::I { NSURL *url = NSURLFromImageSource(imageSource); - if (!url) { + if (url == nullptr) { RCTLogError(@"URI parsing error."); return nil; } diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h index 908cfc0b612b..dac572b1e26b 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h @@ -70,7 +70,7 @@ static inline facebook::react::SharedEventEmitter RCTUnwrapEventEmitter(NSData * auto weakPtr = dynamic_cast *>( (std::weak_ptr *)data.bytes); - if (weakPtr) { + if (weakPtr != nullptr) { return weakPtr->lock(); } diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm index 39093efef09c..82151e6fb8bd 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm @@ -36,7 +36,7 @@ static RCTFontProperties RCTResolveFontProperties( RCTFontProperties fontProperties, RCTFontProperties baseFontProperties) { - fontProperties.family = fontProperties.family.length ? fontProperties.family : baseFontProperties.family; + fontProperties.family = (fontProperties.family.length != 0u) ? fontProperties.family : baseFontProperties.family; fontProperties.size = !isnan(fontProperties.size) ? fontProperties.size : baseFontProperties.size; fontProperties.weight = !isnan(fontProperties.weight) ? fontProperties.weight : baseFontProperties.weight; fontProperties.style = @@ -50,7 +50,7 @@ static RCTFontStyle RCTGetFontStyle(UIFont *font) { NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue]; - if (symbolicTraits & UIFontDescriptorTraitItalic) { + if ((symbolicTraits & UIFontDescriptorTraitItalic) != 0u) { return RCTFontStyleItalic; } @@ -167,79 +167,79 @@ static RCTFontStyle RCTGetFontStyle(UIFont *font) }; }); - if (fontVariant & RCTFontVariantSmallCaps) { + if ((fontVariant & RCTFontVariantSmallCaps) != 0) { [fontFeatures addObject:mapping[RCTFontVariantSmallCaps]]; } - if (fontVariant & RCTFontVariantOldstyleNums) { + if ((fontVariant & RCTFontVariantOldstyleNums) != 0) { [fontFeatures addObject:mapping[RCTFontVariantOldstyleNums]]; } - if (fontVariant & RCTFontVariantLiningNums) { + if ((fontVariant & RCTFontVariantLiningNums) != 0) { [fontFeatures addObject:mapping[RCTFontVariantLiningNums]]; } - if (fontVariant & RCTFontVariantTabularNums) { + if ((fontVariant & RCTFontVariantTabularNums) != 0) { [fontFeatures addObject:mapping[RCTFontVariantTabularNums]]; } - if (fontVariant & RCTFontVariantProportionalNums) { + if ((fontVariant & RCTFontVariantProportionalNums) != 0) { [fontFeatures addObject:mapping[RCTFontVariantProportionalNums]]; } - if (fontVariant & RCTFontVariantStylisticOne) { + if ((fontVariant & RCTFontVariantStylisticOne) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticOne]]; } - if (fontVariant & RCTFontVariantStylisticTwo) { + if ((fontVariant & RCTFontVariantStylisticTwo) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticTwo]]; } - if (fontVariant & RCTFontVariantStylisticThree) { + if ((fontVariant & RCTFontVariantStylisticThree) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticThree]]; } - if (fontVariant & RCTFontVariantStylisticFour) { + if ((fontVariant & RCTFontVariantStylisticFour) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticFour]]; } - if (fontVariant & RCTFontVariantStylisticFive) { + if ((fontVariant & RCTFontVariantStylisticFive) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticFive]]; } - if (fontVariant & RCTFontVariantStylisticSix) { + if ((fontVariant & RCTFontVariantStylisticSix) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticSix]]; } - if (fontVariant & RCTFontVariantStylisticSeven) { + if ((fontVariant & RCTFontVariantStylisticSeven) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticSeven]]; } - if (fontVariant & RCTFontVariantStylisticEight) { + if ((fontVariant & RCTFontVariantStylisticEight) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticEight]]; } - if (fontVariant & RCTFontVariantStylisticNine) { + if ((fontVariant & RCTFontVariantStylisticNine) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticNine]]; } - if (fontVariant & RCTFontVariantStylisticTen) { + if ((fontVariant & RCTFontVariantStylisticTen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticTen]]; } - if (fontVariant & RCTFontVariantStylisticEleven) { + if ((fontVariant & RCTFontVariantStylisticEleven) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticEleven]]; } - if (fontVariant & RCTFontVariantStylisticTwelve) { + if ((fontVariant & RCTFontVariantStylisticTwelve) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticTwelve]]; } - if (fontVariant & RCTFontVariantStylisticThirteen) { + if ((fontVariant & RCTFontVariantStylisticThirteen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticThirteen]]; } - if (fontVariant & RCTFontVariantStylisticFourteen) { + if ((fontVariant & RCTFontVariantStylisticFourteen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticFourteen]]; } - if (fontVariant & RCTFontVariantStylisticFifteen) { + if ((fontVariant & RCTFontVariantStylisticFifteen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticFifteen]]; } - if (fontVariant & RCTFontVariantStylisticSixteen) { + if ((fontVariant & RCTFontVariantStylisticSixteen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticSixteen]]; } - if (fontVariant & RCTFontVariantStylisticSeventeen) { + if ((fontVariant & RCTFontVariantStylisticSeventeen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticSeventeen]]; } - if (fontVariant & RCTFontVariantStylisticEighteen) { + if ((fontVariant & RCTFontVariantStylisticEighteen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticEighteen]]; } - if (fontVariant & RCTFontVariantStylisticNineteen) { + if ((fontVariant & RCTFontVariantStylisticNineteen) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticNineteen]]; } - if (fontVariant & RCTFontVariantStylisticTwenty) { + if ((fontVariant & RCTFontVariantStylisticTwenty) != 0) { [fontFeatures addObject:mapping[RCTFontVariantStylisticTwenty]]; } @@ -258,13 +258,13 @@ static RCTFontStyle RCTGetFontStyle(UIFont *font) { std::lock_guard lock(fontCacheMutex); - if (!fontCache) { + if (fontCache == nullptr) { fontCache = [NSCache new]; } font = [fontCache objectForKey:cacheKey]; } - if (!font) { + if (font == nullptr) { font = [UIFont systemFontOfSize:effectiveFontSize weight:fontProperties.weight]; if (fontProperties.style == RCTFontStyleItalic) { @@ -308,7 +308,7 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f CGFloat effectiveFontSize = fontProperties.sizeMultiplier * fontProperties.size; UIFont *font; UIFontDescriptorSystemDesign design = RCTGetFontDescriptorSystemDesign([fontProperties.family lowercaseString]); - if (design) { + if (design != nullptr) { // Create a system font which `-fontDescriptorWithDesign:` asks for // (see: // https://developer.apple.com/documentation/uikit/uifontdescriptor/3151797-fontdescriptorwithdesign?language=objc) @@ -328,9 +328,9 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f // Gracefully handle being given a font name rather than font family, for // example: "Helvetica Light Oblique" rather than just "Helvetica". font = [UIFont fontWithName:fontProperties.family size:effectiveFontSize]; - if (font) { + if (font != nullptr) { fontNames = [UIFont fontNamesForFamilyName:font.familyName]; - fontWeight = fontWeight ?: RCTGetFontWeight(font); + fontWeight = (fontWeight != 0.0) ?: RCTGetFontWeight(font); } else { // Failback to system font. font = [UIFont systemFontOfSize:effectiveFontSize weight:fontProperties.weight]; @@ -354,7 +354,7 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f } } - if (!font) { + if (font == nullptr) { // If we still don't have a match at least return the first font in the // fontFamily This is to support built-in font Zapfino and other custom // single font families like Impact diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index 40b88bd0096d..3b4109c44aec 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -116,7 +116,7 @@ - (void)drawAttributedString:(AttributedString)attributedString UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) cornerRadius:2]; - if (highlightPath) { + if (highlightPath != nullptr) { [highlightPath appendPath:path]; } else { highlightPath = path; @@ -159,7 +159,7 @@ - (void)processTruncatedAttributedText:(NSTextStorage *)textStorage id attribute = [textStorage attribute:key atIndex:characterRange.location - 1 effectiveRange:nil]; - if (attribute) { + if (attribute != nullptr) { [textStorage addAttribute:key value:attribute range:characterRange]; } } @@ -303,7 +303,7 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString inRange:characterRange options:0 usingBlock:^(NSString *value, NSRange range, BOOL *pause) { - if (!value) { + if (value == nullptr) { return; } @@ -399,7 +399,7 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage inRange:NSMakeRange(0, textStorage.length) options:0 usingBlock:^(NSTextAttachment *attachment, NSRange range, BOOL *stop) { - if (!attachment) { + if (attachment == nullptr) { return; } diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm index 7f032d78cf64..2b3ec31d9d34 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/TextLayoutManager.mm @@ -68,7 +68,7 @@ (NSAttributedString *)unwrapManagedObject(attributedStringBox.getOpaquePointer()); auto telemetry = TransactionTelemetry::threadLocalTelemetry(); - if (telemetry) { + if (telemetry != nullptr) { telemetry->willMeasureText(); } @@ -77,7 +77,7 @@ layoutContext:layoutContext layoutConstraints:layoutConstraints]; - if (telemetry) { + if (telemetry != nullptr) { telemetry->didMeasureText(); } diff --git a/packages/react-native/ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.mm b/packages/react-native/ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.mm index 3ba93dc09589..82a7008ee846 100644 --- a/packages/react-native/ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.mm +++ b/packages/react-native/ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.mm @@ -33,7 +33,7 @@ id convertFollyDynamicToId(const folly::dynamic &dyn) NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:dyn.size()]; for (const auto &elem : dyn) { id value = convertFollyDynamicToId(elem); - if (value) { + if (value != nullptr) { [array addObject:value]; } } @@ -44,7 +44,7 @@ id convertFollyDynamicToId(const folly::dynamic &dyn) for (const auto &elem : dyn.items()) { id key = convertFollyDynamicToId(elem.first); id value = convertFollyDynamicToId(elem.second); - if (key && value) { + if ((key != nullptr) && (value != nullptr)) { dict[key] = value; } } diff --git a/packages/rn-tester/NativeComponentExample/ios/RNTLegacyView.mm b/packages/rn-tester/NativeComponentExample/ios/RNTLegacyView.mm index 3577ac72e82c..08ddadd02a57 100644 --- a/packages/rn-tester/NativeComponentExample/ios/RNTLegacyView.mm +++ b/packages/rn-tester/NativeComponentExample/ios/RNTLegacyView.mm @@ -17,7 +17,7 @@ - (void)setBackgroundColor:(UIColor *)backgroundColor - (void)emitEvent { - if (!self.onColorChanged) { + if (self.onColorChanged == nullptr) { return; } CGFloat hue = 0.0; diff --git a/packages/rn-tester/NativeComponentExample/ios/RNTMyLegacyNativeViewManager.mm b/packages/rn-tester/NativeComponentExample/ios/RNTMyLegacyNativeViewManager.mm index d0baab512c4b..35585daff495 100644 --- a/packages/rn-tester/NativeComponentExample/ios/RNTMyLegacyNativeViewManager.mm +++ b/packages/rn-tester/NativeComponentExample/ios/RNTMyLegacyNativeViewManager.mm @@ -68,7 +68,7 @@ + (BOOL)requiresMainQueueSetup + (UIView *)getViewByTag:(NSDictionary *)viewRegistry reactTag:(nonnull NSNumber *)reactTag { UIView *view = viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[RNTLegacyView class]]) { + if ((view == nullptr) || ![view isKindOfClass:[RNTLegacyView class]]) { RCTLogError(@"Cannot find RNTLegacyView with tag #%@", reactTag); return NULL; } diff --git a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewManager.mm b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewManager.mm index cb4669abe9a7..147da4612ff2 100644 --- a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewManager.mm +++ b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewManager.mm @@ -27,7 +27,7 @@ @implementation RNTMyNativeViewManager { [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { UIView *view = viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[UIView class]]) { + if ((view == nullptr) || ![view isKindOfClass:[UIView class]]) { RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); return; } From b0db8aa26b0abb8280dfb9188db8399069570937 Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Thu, 4 Sep 2025 03:47:53 -0700 Subject: [PATCH 0045/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53592) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53592 Reviewed By: rshest Differential Revision: D81569365 fbshipit-source-id: 88ec1b964a37774f29df9cbabca3c0e2c5ee4c53 --- .../React/Fabric/Mounting/RCTComponentViewFactory.mm | 6 +++--- .../React/Fabric/Utils/PlatformRunLoopObserver.mm | 2 +- packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm index 6f7cc61a8e7e..c696184983f7 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -132,18 +132,18 @@ - (void)_registerComponentIfPossible:(const std::string &)name // Fallback 1: Call provider function for component view class. Class klass = RCTComponentViewClassWithName(name.c_str()); - if (klass) { + if (klass != nullptr) { [self registerComponentViewClass:klass]; return; } // Fallback 2: Ask the provider and check in the dictionary provided - if (self.thirdPartyFabricComponentsProvider) { + if (self.thirdPartyFabricComponentsProvider != nullptr) { // Test whether a provider has been passed to avoid potentially expensive conversions // between C++ and ObjC strings. NSString *objcName = [NSString stringWithCString:name.c_str() encoding:NSUTF8StringEncoding]; klass = self.thirdPartyFabricComponentsProvider.thirdPartyFabricComponents[objcName]; - if (klass) { + if (klass != nullptr) { [self registerComponentViewClass:klass]; return; } diff --git a/packages/react-native/React/Fabric/Utils/PlatformRunLoopObserver.mm b/packages/react-native/React/Fabric/Utils/PlatformRunLoopObserver.mm index 65f48b48a4f6..c11d48cdb17d 100644 --- a/packages/react-native/React/Fabric/Utils/PlatformRunLoopObserver.mm +++ b/packages/react-native/React/Fabric/Utils/PlatformRunLoopObserver.mm @@ -54,7 +54,7 @@ static CFRunLoopActivity toCFRunLoopActivity(RunLoopObserver::Activity activity) mainRunLoopObserver_ = CFRunLoopObserverCreateWithHandler( NULL /* allocator */, toCFRunLoopActivity(activities_) /* activities */, - true /* repeats */, + 1u /* repeats */, 0 /* order */, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { auto strongOwner = owner.lock(); diff --git a/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm b/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm index 54f3b79894ca..54633558d177 100644 --- a/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm +++ b/packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm @@ -50,7 +50,7 @@ static CGRect insetRect(CGRect rect, CGFloat left, CGFloat top, CGFloat right, C static CGColorRef colorRefFromSharedColor(const SharedColor &color) { CGColorRef colorRef = RCTUIColorFromSharedColor(color).CGColor; - return colorRef ? colorRef : [UIColor blackColor].CGColor; + return (colorRef != nullptr) ? colorRef : [UIColor blackColor].CGColor; } static CALayer *initBoxShadowLayer(const BoxShadow &shadow, CGSize layerSize) From 7a4d5ad644c266d62eee70cad7480f30f13e8ae6 Mon Sep 17 00:00:00 2001 From: generatedunixname537391475639613 Date: Thu, 4 Sep 2025 03:49:37 -0700 Subject: [PATCH 0046/1110] xplat/js/react-native-github/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt (#53597) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53597 Reviewed By: cortinico Differential Revision: D81662100 fbshipit-source-id: f41c89a059dd0d8e312e5edc07172e1d8cac6597 --- .../GenerateAutolinkingNewArchitecturesFileTaskTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt index 9b86b0af306c..6698555eb3b8 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt @@ -34,9 +34,9 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { val inputFile = tempFolder.newFile("config.json") val task = - createTestTask { - it.generatedOutputDirectory.set(outputFolder) - it.autolinkInputFile.set(inputFile) + createTestTask { task -> + task.generatedOutputDirectory.set(outputFolder) + task.autolinkInputFile.set(inputFile) } assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) From 95b187bb3706b115b9c463cd2293f54ddf1ef718 Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Thu, 4 Sep 2025 03:56:08 -0700 Subject: [PATCH 0047/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53596) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53596 Reviewed By: rshest Differential Revision: D81574342 fbshipit-source-id: 9423d3341a9c349d7e7519b5acb7ee41f6ceb2b3 --- .../NativeAnimation/Nodes/RCTAnimatedNode.mm | 18 +++++++++--------- .../Nodes/RCTInterpolationAnimatedNode.mm | 6 +++--- .../Nodes/RCTObjectAnimatedNode.mm | 2 +- .../Nodes/RCTStyleAnimatedNode.mm | 6 +++--- .../Nodes/RCTTrackingAnimatedNode.mm | 2 +- .../Nodes/RCTTransformAnimatedNode.mm | 2 +- .../RCTNativeAnimatedNodesManager.mm | 18 +++++++++--------- .../Libraries/Network/RCTDataRequestHandler.mm | 8 ++++---- .../Libraries/Network/RCTHTTPRequestHandler.mm | 8 ++++---- .../Libraries/Settings/RCTSettingsManager.mm | 4 ++-- 10 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.mm index c17f29fd447c..b858f019093b 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.mm @@ -16,7 +16,7 @@ @implementation RCTAnimatedNode { - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _nodeTag = tag; _config = [config copy]; } @@ -37,10 +37,10 @@ - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary - (void)addChild:(RCTAnimatedNode *)child { - if (!_childNodes) { + if (_childNodes == nullptr) { _childNodes = [NSMapTable strongToWeakObjectsMapTable]; } - if (child) { + if (child != nullptr) { [_childNodes setObject:child forKey:child.nodeTag]; [child onAttachedToNode:self]; } @@ -48,10 +48,10 @@ - (void)addChild:(RCTAnimatedNode *)child - (void)removeChild:(RCTAnimatedNode *)child { - if (!_childNodes) { + if (_childNodes == nullptr) { return; } - if (child) { + if (child != nullptr) { [_childNodes removeObjectForKey:child.nodeTag]; [child onDetachedFromNode:self]; } @@ -59,20 +59,20 @@ - (void)removeChild:(RCTAnimatedNode *)child - (void)onAttachedToNode:(RCTAnimatedNode *)parent { - if (!_parentNodes) { + if (_parentNodes == nullptr) { _parentNodes = [NSMapTable strongToWeakObjectsMapTable]; } - if (parent) { + if (parent != nullptr) { [_parentNodes setObject:parent forKey:parent.nodeTag]; } } - (void)onDetachedFromNode:(RCTAnimatedNode *)parent { - if (!_parentNodes) { + if (_parentNodes == nullptr) { return; } - if (parent) { + if (parent != nullptr) { [_parentNodes removeObjectForKey:parent.nodeTag]; } } diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm index 45846b8471c9..b45410f9c0c2 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.mm @@ -86,7 +86,7 @@ @implementation RCTInterpolationAnimatedNode { - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config { - if ((self = [super initWithTag:tag config:config])) { + if ((self = [super initWithTag:tag config:config]) != nullptr) { _inputRange = config[@"inputRange"]; NSArray *outputRangeConfig = config[@"outputRange"]; @@ -104,7 +104,7 @@ - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary switch (_outputType) { case RCTInterpolationOutputColor: { UIColor *color = [RCTConvert UIColor:value]; - [outputRange addObject:color ? color : [UIColor whiteColor]]; + [outputRange addObject:(color != nullptr) ? color : [UIColor whiteColor]]; break; } case RCTInterpolationOutputString: @@ -141,7 +141,7 @@ - (void)onDetachedFromNode:(RCTAnimatedNode *)parent - (void)performUpdate { [super performUpdate]; - if (!_parentNode) { + if (_parentNode == nullptr) { return; } diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTObjectAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTObjectAnimatedNode.mm index 81b9ef37a8b6..1c5d83bc0304 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTObjectAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTObjectAnimatedNode.mm @@ -48,7 +48,7 @@ - (id)_convertValue:(id)value if ([value isKindOfClass:[NSDictionary class]]) { NSDictionary *dict = (NSDictionary *)value; id nodeTag = [dict objectForKey:NODE_TAG_KEY]; - if (nodeTag && [nodeTag isKindOfClass:[NSNumber class]]) { + if ((nodeTag != nullptr) && [nodeTag isKindOfClass:[NSNumber class]]) { RCTAnimatedNode *node = [self.parentNodes objectForKey:(NSNumber *)nodeTag]; if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm index 7673db6323e4..8be4e22bb0b4 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.mm @@ -18,7 +18,7 @@ @implementation RCTStyleAnimatedNode { - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config { - if ((self = [super initWithTag:tag config:config])) { + if ((self = [super initWithTag:tag config:config]) != nullptr) { _propsDictionary = [NSMutableDictionary new]; } return self; @@ -36,11 +36,11 @@ - (void)performUpdate NSDictionary *style = self.config[@"style"]; [style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) { RCTAnimatedNode *node = [self.parentNodes objectForKey:nodeTag]; - if (node) { + if (node != nullptr) { if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTValueAnimatedNode *valueAnimatedNode = (RCTValueAnimatedNode *)node; id animatedObject = valueAnimatedNode.animatedObject; - if (animatedObject) { + if (animatedObject != nullptr) { _propsDictionary[property] = animatedObject; } else { _propsDictionary[property] = @(valueAnimatedNode.value); diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTrackingAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTrackingAnimatedNode.mm index 85d208680f3b..900a23d90713 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTrackingAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTrackingAnimatedNode.mm @@ -18,7 +18,7 @@ @implementation RCTTrackingAnimatedNode { - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config { - if ((self = [super initWithTag:tag config:config])) { + if ((self = [super initWithTag:tag config:config]) != nullptr) { _animationId = config[@"animationId"]; _toValueNodeTag = config[@"toValue"]; _valueNodeTag = config[@"value"]; diff --git a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.mm b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.mm index 8c58d6d33cab..a0dcef13761f 100644 --- a/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.mm +++ b/packages/react-native/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.mm @@ -14,7 +14,7 @@ @implementation RCTTransformAnimatedNode { - (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary *)config { - if ((self = [super initWithTag:tag config:config])) { + if ((self = [super initWithTag:tag config:config]) != nullptr) { _propsDictionary = [NSMutableDictionary new]; } return self; diff --git a/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm b/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm index 206fdd26f851..11146ca1f23c 100644 --- a/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm +++ b/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm @@ -59,7 +59,7 @@ @implementation RCTNativeAnimatedNodesManager { - (instancetype)initWithBridge:(nullable RCTBridge *)bridge surfacePresenter:(id)surfacePresenter { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _bridge = bridge; _surfacePresenter = surfacePresenter; _animationNodes = [NSMutableDictionary new]; @@ -72,7 +72,7 @@ - (instancetype)initWithBridge:(nullable RCTBridge *)bridge - (BOOL)isNodeManagedByFabric:(NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; - if (node) { + if (node != nullptr) { return [node isManagedByFabric]; } return false; @@ -106,7 +106,7 @@ - (void)createAnimatedNode:(NSNumber *)tag config:(NSDictionary NSString *nodeType = [RCTConvert NSString:config[@"type"]]; Class nodeClass = map[nodeType]; - if (!nodeClass) { + if (nodeClass == nullptr) { RCTLogError(@"Animated node type %@ not supported natively", nodeType); return; } @@ -187,7 +187,7 @@ - (void)restoreDefaultValues:(NSNumber *)nodeTag - (void)dropAnimatedNode:(NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; - if (node) { + if (node != nullptr) { [node detachNode]; [_animationNodes removeObjectForKey:tag]; } @@ -345,7 +345,7 @@ - (void)addAnimatedEventToView:(NSNumber *)viewTag NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]]; RCTAnimatedNode *node = _animationNodes[nodeTag]; - if (!node) { + if (node == nullptr) { RCTLogError(@"Animated node with tag %@ does not exist", nodeTag); return; } @@ -407,7 +407,7 @@ - (void)handleAnimatedEvent:(id)event NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, RCTNormalizeAnimatedEventName(event.eventName)]; NSMutableArray *driversForKey = _eventDrivers[key]; - if (driversForKey) { + if (driversForKey != nullptr) { for (RCTEventAnimation *driver in driversForKey) { [self stopAnimationsForNode:driver.valueNode]; [driver updateWithEvent:event]; @@ -439,7 +439,7 @@ - (void)stopListeningToAnimatedNodeValue:(NSNumber *)tag - (void)startAnimationLoopIfNeeded { - if (!_displayLink && _activeAnimations.count > 0) { + if ((_displayLink == nullptr) && _activeAnimations.count > 0) { _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } @@ -454,7 +454,7 @@ - (void)stopAnimationLoopIfNeeded - (void)stopAnimationLoop { - if (_displayLink) { + if (_displayLink != nullptr) { [_displayLink invalidate]; _displayLink = nil; } @@ -486,7 +486,7 @@ - (void)stepAnimations:(CADisplayLink *)displaylink NSArray *eventAnimations = _eventDrivers[key]; for (RCTEventAnimation *animation in eventAnimations) { NSNumber *nodeTag = [animation.valueNode nodeTag]; - if (nodeTag) { + if (nodeTag != nullptr) { [tags addObject:nodeTag]; } for (NSNumber *childNodeKey in [animation.valueNode childNodes]) { diff --git a/packages/react-native/Libraries/Network/RCTDataRequestHandler.mm b/packages/react-native/Libraries/Network/RCTDataRequestHandler.mm index c91f89e22bd3..d52f6ba8ad6d 100644 --- a/packages/react-native/Libraries/Network/RCTDataRequestHandler.mm +++ b/packages/react-native/Libraries/Network/RCTDataRequestHandler.mm @@ -25,7 +25,7 @@ @implementation RCTDataRequestHandler { - (void)invalidate { std::lock_guard lock(_operationHandlerMutexLock); - if (_queue) { + if (_queue != nullptr) { for (NSOperation *operation in _queue.operations) { if (!operation.isCancelled && !operation.isFinished) { [operation cancel]; @@ -44,7 +44,7 @@ - (NSOperation *)sendRequest:(NSURLRequest *)request withDelegate:(id lock(_operationHandlerMutexLock); // Lazy setup - if (!_queue) { + if (_queue == nullptr) { _queue = [NSOperationQueue new]; _queue.maxConcurrentOperationCount = 2; } @@ -59,7 +59,7 @@ - (NSOperation *)sendRequest:(NSURLRequest *)request withDelegate:(id lock(_mutex); // Lazy setup - if (!_session && [self isValid]) { + if ((_session == nullptr) && [self isValid]) { // You can override default NSURLSession instance property allowsCellularAccess (default value YES) // by providing the following key to your RN project (edit ios/project/Info.plist file in Xcode): // ReactNetworkForceWifiOnly @@ -80,12 +80,12 @@ - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id_defaults setObject:plist forKey:key]; } else { [self->_defaults removeObjectForKey:key]; From 8d33e1c205b12fe27f4319e6566bb0c088197810 Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Thu, 4 Sep 2025 04:01:12 -0700 Subject: [PATCH 0048/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages (#53593) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53593 Reviewed By: rshest Differential Revision: D81573635 fbshipit-source-id: a367572b7d2b3a9422e47fa05d3c001e607ec0e3 --- .../Libraries/AppDelegate/RCTAppSetupUtils.mm | 8 ++++---- .../RCTDefaultReactNativeFactoryDelegate.mm | 8 +++++--- .../Libraries/Blob/RCTBlobCollector.mm | 2 +- .../Libraries/Image/RCTBundleAssetImageLoader.mm | 4 ++-- .../Libraries/Image/RCTGIFImageDecoder.mm | 4 ++-- .../Libraries/Image/RCTImageBlurUtils.mm | 8 ++++---- .../Libraries/Image/RCTImageStoreManager.mm | 14 +++++++------- .../Libraries/Image/RCTLocalAssetImageLoader.mm | 4 ++-- .../Libraries/LinkingIOS/RCTLinkingManager.mm | 2 +- .../NativeAnimation/Drivers/RCTEventAnimation.mm | 2 +- 10 files changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm index 8a89672bb8f3..469e73182ae2 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm @@ -54,7 +54,7 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) NSArray *RCTAppSetupUnstableModulesRequiringMainQueueSetup(id dependencyProvider) { // For oss, insert core main queue setup modules here - return dependencyProvider ? dependencyProvider.unstableModulesRequiringMainQueueSetup : @[]; + return (dependencyProvider != nullptr) ? dependencyProvider.unstableModulesRequiringMainQueueSetup : @[]; } id RCTAppSetupDefaultModuleFromClass(Class moduleClass, id dependencyProvider) @@ -65,11 +65,11 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) NSArray *classNames = @[]; if (protocol == @protocol(RCTImageURLLoader)) { - classNames = dependencyProvider ? dependencyProvider.imageURLLoaderClassNames : @[]; + classNames = (dependencyProvider != nullptr) ? dependencyProvider.imageURLLoaderClassNames : @[]; } else if (protocol == @protocol(RCTImageDataDecoder)) { - classNames = dependencyProvider ? dependencyProvider.imageDataDecoderClassNames : @[]; + classNames = (dependencyProvider != nullptr) ? dependencyProvider.imageDataDecoderClassNames : @[]; } else if (protocol == @protocol(RCTURLRequestHandler)) { - classNames = dependencyProvider ? dependencyProvider.URLRequestHandlerClassNames : @[]; + classNames = (dependencyProvider != nullptr) ? dependencyProvider.URLRequestHandlerClassNames : @[]; } NSMutableArray *modules = [NSMutableArray new]; diff --git a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm index 07562f178eb8..3b917c1a05cc 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm @@ -78,7 +78,7 @@ - (NSURL *_Nullable)bundleURL - (NSDictionary> *)thirdPartyFabricComponents { - return self.dependencyProvider ? self.dependencyProvider.thirdPartyFabricComponents : @{}; + return (self.dependencyProvider != nullptr) ? self.dependencyProvider.thirdPartyFabricComponents : @{}; } - (void)hostDidStart:(RCTHost *)host @@ -87,13 +87,15 @@ - (void)hostDidStart:(RCTHost *)host - (NSArray *)unstableModulesRequiringMainQueueSetup { - return self.dependencyProvider ? RCTAppSetupUnstableModulesRequiringMainQueueSetup(self.dependencyProvider) : @[]; + return (self.dependencyProvider != nullptr) + ? RCTAppSetupUnstableModulesRequiringMainQueueSetup(self.dependencyProvider) + : @[]; } - (nullable id)getModuleProvider:(const char *)name { NSString *providerName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; - return self.dependencyProvider ? self.dependencyProvider.moduleProviders[providerName] : nullptr; + return (self.dependencyProvider != nullptr) ? self.dependencyProvider.moduleProviders[providerName] : nullptr; } - (std::shared_ptr)getTurboModule:(const std::string &)name diff --git a/packages/react-native/Libraries/Blob/RCTBlobCollector.mm b/packages/react-native/Libraries/Blob/RCTBlobCollector.mm index 9028e089b411..61927afa2ee3 100644 --- a/packages/react-native/Libraries/Blob/RCTBlobCollector.mm +++ b/packages/react-native/Libraries/Blob/RCTBlobCollector.mm @@ -31,7 +31,7 @@ __weak RCTCxxBridge *cxxBridge = (RCTCxxBridge *)blobManager.bridge; [cxxBridge dispatchBlock:^{ - if (!cxxBridge || cxxBridge.runtime == nullptr) { + if ((cxxBridge == nullptr) || cxxBridge.runtime == nullptr) { return; } jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime; diff --git a/packages/react-native/Libraries/Image/RCTBundleAssetImageLoader.mm b/packages/react-native/Libraries/Image/RCTBundleAssetImageLoader.mm index f412e9b74c10..538bf842fcf1 100644 --- a/packages/react-native/Libraries/Image/RCTBundleAssetImageLoader.mm +++ b/packages/react-native/Libraries/Image/RCTBundleAssetImageLoader.mm @@ -50,8 +50,8 @@ - (nullable RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL completionHandler:(RCTImageLoaderCompletionBlock)completionHandler { UIImage *image = RCTImageFromLocalAssetURL(imageURL); - if (image) { - if (progressHandler) { + if (image != nullptr) { + if (progressHandler != nullptr) { progressHandler(1, 1); } completionHandler(nil, image); diff --git a/packages/react-native/Libraries/Image/RCTGIFImageDecoder.mm b/packages/react-native/Libraries/Image/RCTGIFImageDecoder.mm index ca66cda7eb02..92628c6c8f26 100644 --- a/packages/react-native/Libraries/Image/RCTGIFImageDecoder.mm +++ b/packages/react-native/Libraries/Image/RCTGIFImageDecoder.mm @@ -27,7 +27,7 @@ - (BOOL)canDecodeImageData:(NSData *)imageData char header[7] = {}; [imageData getBytes:header length:6]; - return !strcmp(header, "GIF87a") || !strcmp(header, "GIF89a"); + return (strcmp(header, "GIF87a") == 0) || (strcmp(header, "GIF89a") == 0); } - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData @@ -38,7 +38,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData { RCTAnimatedImage *image = [[RCTAnimatedImage alloc] initWithData:imageData scale:scale]; - if (!image) { + if (image == nullptr) { completionHandler(nil, nil); return ^{ }; diff --git a/packages/react-native/Libraries/Image/RCTImageBlurUtils.mm b/packages/react-native/Libraries/Image/RCTImageBlurUtils.mm index e5a17309473b..df9fa1d9a145 100644 --- a/packages/react-native/Libraries/Image/RCTImageBlurUtils.mm +++ b/packages/react-native/Libraries/Image/RCTImageBlurUtils.mm @@ -19,7 +19,7 @@ } // convert to ARGB if it isn't - if (CGImageGetBitsPerPixel(imageRef) != 32 || !((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask))) { + if (CGImageGetBitsPerPixel(imageRef) != 32 || (((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask)) == 0u)) { UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat]; rendererFormat.scale = inputImage.scale; UIGraphicsImageRenderer *const renderer = [[UIGraphicsImageRenderer alloc] initWithSize:inputImage.size @@ -36,11 +36,11 @@ buffer1.rowBytes = buffer2.rowBytes = CGImageGetBytesPerRow(imageRef); size_t bytes = buffer1.rowBytes * buffer1.height; buffer1.data = malloc(bytes); - if (!buffer1.data) { + if (buffer1.data == nullptr) { return inputImage; } buffer2.data = malloc(bytes); - if (!buffer2.data) { + if (buffer2.data == nullptr) { free(buffer1.data); return inputImage; } @@ -60,7 +60,7 @@ return inputImage; } void *tempBuffer = malloc(tempBufferSize); - if (!tempBuffer) { + if (tempBuffer == nullptr) { free(buffer1.data); free(buffer2.data); return inputImage; diff --git a/packages/react-native/Libraries/Image/RCTImageStoreManager.mm b/packages/react-native/Libraries/Image/RCTImageStoreManager.mm index 0f99fbed6b57..b28824556e0e 100644 --- a/packages/react-native/Libraries/Image/RCTImageStoreManager.mm +++ b/packages/react-native/Libraries/Image/RCTImageStoreManager.mm @@ -48,7 +48,7 @@ - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)(void))block { dispatch_async(_methodQueue, ^{ [self removeImageForTag:imageTag]; - if (block) { + if (block != nullptr) { block(); } }); @@ -58,7 +58,7 @@ - (NSString *)_storeImageData:(NSData *)imageData { RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread"); - if (!_store) { + if (_store == nullptr) { _store = [NSMutableDictionary new]; _id = 0; } @@ -112,7 +112,7 @@ - (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))bloc : (RCTResponseSenderBlock)errorCallback) { NSData *imageData = _store[imageTag]; - if (!imageData) { + if (imageData == nullptr) { errorCallback( @[ RCTJSErrorFromNSError(RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag])) ]); return; @@ -132,7 +132,7 @@ - (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))bloc // Dispatching to a background thread to perform base64 decoding dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; - if (imageData) { + if (imageData != nullptr) { dispatch_async(self->_methodQueue, ^{ successCallback(@[ [self _storeImageData:imageData] ]); }); @@ -164,14 +164,14 @@ - (id)sendRequest:(NSURLRequest *)request withDelegate:(id_store[imageTag]; - if (!imageData) { + if (imageData == nullptr) { NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]); [delegate URLRequest:cancellationBlock didCompleteWithError:error]; return; } CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); - if (!sourceRef) { + if (sourceRef == nullptr) { NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to decode data for imageTag: %@", imageTag]); [delegate URLRequest:cancellationBlock didCompleteWithError:error]; @@ -197,7 +197,7 @@ - (id)sendRequest:(NSURLRequest *)request withDelegate:(id *)eventPath valueNode:(RCTValueAnimatedNode *)valueNode { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _eventPath = eventPath; _valueNode = valueNode; } From 10a46f7b5245fb9f688a235606733d8ebea60e9a Mon Sep 17 00:00:00 2001 From: Andrew Datsenko Date: Thu, 4 Sep 2025 07:19:56 -0700 Subject: [PATCH 0049/1110] Add support for JS coverage (#53410) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53410 Changelog: [Internal] Adding babel-istanbul-plugin to instrument bundle code with coverage reporting. Metro will transform source code only when coverage flag is set up globally in jest. Coverage map is then provided by runner as part of test result. Reviewed By: sammy-SC Differential Revision: D80716433 fbshipit-source-id: 3831f227f8793f874f0d2366759bb6916e747c72 --- .../config/metro-babel-transformer.flow.js | 18 ++++++++++++++++++ private/react-native-fantom/runner/bundling.js | 8 ++++++++ .../runner/global-setup/build.js | 1 + private/react-native-fantom/runner/runner.js | 9 +++++++++ private/react-native-fantom/runtime/setup.js | 4 ++++ 5 files changed, 40 insertions(+) diff --git a/private/react-native-fantom/config/metro-babel-transformer.flow.js b/private/react-native-fantom/config/metro-babel-transformer.flow.js index b3a07cad935f..648accf29869 100644 --- a/private/react-native-fantom/config/metro-babel-transformer.flow.js +++ b/private/react-native-fantom/config/metro-babel-transformer.flow.js @@ -26,6 +26,24 @@ const transform: BabelTransformer['transform'] = ( ...(args.plugins ?? []), // $FlowExpectedError[untyped-import] require('./babel-plugins/inject-debugger-statements-in-tests'), + ...(args.options.customTransformOptions?.collectCoverage === 'true' + ? [ + [ + require.resolve('babel-plugin-istanbul'), + { + include: [ + 'packages/react-native/Libraries/**/*.js', + 'packages/react-native/src/**/*.js', + 'packages/virtualized-lists/**/*.js', + ], + exclude: [ + 'packages/react-native/Libraries/Renderer/**', + '**/__tests__/**', + ], + }, + ], + ] + : []), ], }; return MetroBabelTransformer.transform(processedArgs); diff --git a/private/react-native-fantom/runner/bundling.js b/private/react-native-fantom/runner/bundling.js index 0d4077237b31..4b4e6c419b2d 100644 --- a/private/react-native-fantom/runner/bundling.js +++ b/private/react-native-fantom/runner/bundling.js @@ -15,6 +15,9 @@ import path from 'path'; type BundleOptions = { ...RunBuildOptions, + customTransformOptions: ?{ + collectCoverage: boolean, + }, out: $NonMaybeType, testPath: string, }; @@ -94,6 +97,7 @@ function getBundleBaseURL({ dev, sourceMap, sourceMapUrl, + customTransformOptions, }: BundleOptions): URL { const requestPath = path.relative(PROJECT_ROOT, entry).replace(/\.js$/, ''); const port = getMetroPort(); @@ -120,6 +124,10 @@ function getBundleBaseURL({ baseURL.searchParams.append('sourceMapUrl', sourceMapUrl); } + if (customTransformOptions?.collectCoverage) { + baseURL.searchParams.append('transform.collectCoverage', 'true'); + } + return baseURL; } diff --git a/private/react-native-fantom/runner/global-setup/build.js b/private/react-native-fantom/runner/global-setup/build.js index 8ffb87934716..929984ab0eb4 100644 --- a/private/react-native-fantom/runner/global-setup/build.js +++ b/private/react-native-fantom/runner/global-setup/build.js @@ -78,6 +78,7 @@ async function warmUpMetro(isOptimizedMode: boolean): Promise { platform: 'android', minify: isOptimizedMode, dev: !isOptimizedMode, + customTransformOptions: undefined, }); try { diff --git a/private/react-native-fantom/runner/runner.js b/private/react-native-fantom/runner/runner.js index 4c1fae688bf0..ae019a345306 100644 --- a/private/react-native-fantom/runner/runner.js +++ b/private/react-native-fantom/runner/runner.js @@ -9,6 +9,7 @@ */ import type { + CoverageMap, FailureDetail, TestCaseResult, TestSuiteResult, @@ -193,6 +194,7 @@ function generateBytecodeBundle({ module.exports = async function runTest( globalConfig: { updateSnapshot: 'all' | 'new' | 'none', + collectCoverage: boolean, ... }, config: { @@ -205,6 +207,7 @@ module.exports = async function runTest( runtime: {...}, testPath: string, ): mixed { + let coverageMap: CoverageMap | void; const snapshotResolver = await buildSnapshotResolver(config); const snapshotPath = snapshotResolver.resolveSnapshotPath(testPath); const snapshotState = new SnapshotState(snapshotPath, { @@ -339,6 +342,9 @@ module.exports = async function runTest( dev: !testConfig.isJsOptimized, sourceMap: true, sourceMapUrl: sourceMapPath, + customTransformOptions: { + collectCoverage: globalConfig.collectCoverage, + }, }; await createBundle({ @@ -456,6 +462,8 @@ module.exports = async function runTest( } testResultsByConfig.push(testResults); + + coverageMap = processedResult.coverageMap; } const endTime = Date.now(); @@ -481,6 +489,7 @@ module.exports = async function runTest( globalConfig, testPath, ), + coverage: coverageMap, leaks: false, openHandles: [], perfStats: { diff --git a/private/react-native-fantom/runtime/setup.js b/private/react-native-fantom/runtime/setup.js index e2bc57d38b08..f7512981be28 100644 --- a/private/react-native-fantom/runtime/setup.js +++ b/private/react-native-fantom/runtime/setup.js @@ -36,9 +36,12 @@ export type FailureDetail = { cause?: FailureDetail, }; +export opaque type CoverageMap = mixed; + export type TestSuiteResult = | { testResults: Array, + coverageMap?: CoverageMap, } | { error: FailureDetail, @@ -465,6 +468,7 @@ global.$$RunTests$$ = () => { } else { reportTestSuiteResult({ testResults: runTest(), + coverageMap: global.__coverage__, }); } }; From 30432addfbaeb6cede6305024c7c09fa1eb23ba9 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 4 Sep 2025 07:42:12 -0700 Subject: [PATCH 0050/1110] Gate legacy debugger behind a preprocessor directive (#53578) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53578 Changelog: [Internal] Adds a new preprocessor directive which should be set when the new Hermes is being used. This directive will disable the legacy debugger which isn't supported by it. Reviewed By: cipolleschi, cortinico Differential Revision: D81035112 fbshipit-source-id: b30ae348b3419ec2d064dfe7f91c9d664a66f5cf --- .../jni/react/hermes/reactexecutor/CMakeLists.txt | 4 ++++ .../jni/react/runtime/hermes/jni/CMakeLists.txt | 4 ++++ .../src/main/jni/react/runtime/jni/CMakeLists.txt | 4 ++++ .../ReactCommon/hermes/executor/CMakeLists.txt | 4 ++++ .../hermes/executor/HermesExecutorFactory.cpp | 13 ++++++++----- .../hermes/inspector-modern/CMakeLists.txt | 4 ++++ .../inspector-modern/chrome/ConnectionDemux.cpp | 4 ++-- .../inspector-modern/chrome/ConnectionDemux.h | 4 ++-- .../hermes/inspector-modern/chrome/Registration.cpp | 4 ++-- .../hermes/inspector-modern/chrome/Registration.h | 4 ++-- .../ReactCommon/react/runtime/CMakeLists.txt | 4 ++++ .../ReactCommon/react/runtime/hermes/CMakeLists.txt | 4 ++++ .../react/runtime/hermes/HermesInstance.cpp | 10 +++++++--- 13 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt index 920732a0db8e..a73ab1c5297b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt @@ -27,4 +27,8 @@ target_link_libraries( target_compile_reactnative_options(hermes_executor PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(hermes_executor PRIVATE -DHERMES_ENABLE_DEBUGGER=1) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(hermes_executor PRIVATE -DHERMES_V1_ENABLED=1) + endif() endif() diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt index 13f7c86023d1..42612452f2e2 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt @@ -29,4 +29,8 @@ target_link_libraries(hermesinstancejni target_compile_reactnative_options(hermesinstancejni PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(hermesinstancejni PRIVATE -DHERMES_ENABLE_DEBUGGER=1) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(hermesinstancejni PRIVATE -DHERMES_V1_ENABLED=1) + endif() endif () diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt index 8987aed03763..8b88a0c2c9e6 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt @@ -19,6 +19,10 @@ add_library(rninstance target_compile_reactnative_options(rninstance PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(rninstance PRIVATE -DHERMES_ENABLE_DEBUGGER=1) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(rninstance PRIVATE -DHERMES_V1_ENABLED=1) + endif() endif () target_merge_so(rninstance) diff --git a/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt b/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt index 3731086f6d7d..741ced76e886 100644 --- a/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt +++ b/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt @@ -32,6 +32,10 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) PRIVATE -DHERMES_ENABLE_DEBUGGER=1 ) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(hermes_executor_common PRIVATE -DHERMES_V1_ENABLED=1) + endif() else() target_compile_options( hermes_executor_common diff --git a/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp b/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp index 0b72b725feaa..8d58bb3774a7 100644 --- a/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp +++ b/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp @@ -14,8 +14,11 @@ #include #include + +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) #include #include +#endif using namespace facebook::hermes; using namespace facebook::jsi; @@ -24,7 +27,7 @@ namespace facebook::react { namespace { -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) class HermesExecutorRuntimeAdapter : public facebook::hermes::inspector_modern::RuntimeAdapter { @@ -59,7 +62,7 @@ class HermesExecutorRuntimeAdapter std::shared_ptr thread_; }; -#endif // HERMES_ENABLE_DEBUGGER +#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) struct ReentrancyCheck { // This is effectively a very subtle and complex assert, so only @@ -144,7 +147,7 @@ class DecoratedRuntime : public jsi::WithRuntimeDecorator { const std::string& debuggerName) : jsi::WithRuntimeDecorator(*runtime, reentrancyCheck_), runtime_(std::move(runtime)) { -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) enableDebugger_ = enableDebugger; if (enableDebugger_) { std::shared_ptr rt(runtime_, &hermesRuntime); @@ -159,7 +162,7 @@ class DecoratedRuntime : public jsi::WithRuntimeDecorator { } ~DecoratedRuntime() { -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) if (enableDebugger_) { facebook::hermes::inspector_modern::chrome::disableDebugging(debugToken_); } @@ -176,7 +179,7 @@ class DecoratedRuntime : public jsi::WithRuntimeDecorator { std::shared_ptr runtime_; ReentrancyCheck reentrancyCheck_; -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) bool enableDebugger_; facebook::hermes::inspector_modern::chrome::DebugSessionToken debugToken_; #endif // HERMES_ENABLE_DEBUGGER diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt b/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt index ed2fbd871355..43ceec656215 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt @@ -23,6 +23,10 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) PRIVATE -DHERMES_ENABLE_DEBUGGER=1 ) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(hermes_inspector_modern PRIVATE -DHERMES_V1_ENABLED=1) + endif() endif() target_include_directories(hermes_inspector_modern PUBLIC ${REACT_COMMON_DIR}) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.cpp b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.cpp index 7e0d9831e124..2830d3199d6d 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.cpp +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.cpp @@ -7,7 +7,7 @@ #include "ConnectionDemux.h" -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) #include #include @@ -139,4 +139,4 @@ void ConnectionDemux::removePage(int pageId) { } // namespace facebook::hermes::inspector_modern::chrome -#endif // HERMES_ENABLE_DEBUGGER +#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.h b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.h index 2c0fcd5b6ef1..2fd75e563b23 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.h +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/ConnectionDemux.h @@ -7,7 +7,7 @@ #pragma once -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) #include #include @@ -59,4 +59,4 @@ class ConnectionDemux { } // namespace facebook::hermes::inspector_modern::chrome -#endif // HERMES_ENABLE_DEBUGGER +#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.cpp b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.cpp index c1e000a30997..d935ef0ad6da 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.cpp +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.cpp @@ -8,7 +8,7 @@ #include "Registration.h" #include "ConnectionDemux.h" -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) namespace facebook::hermes::inspector_modern::chrome { @@ -34,4 +34,4 @@ void disableDebugging(DebugSessionToken session) { } // namespace facebook::hermes::inspector_modern::chrome -#endif // HERMES_ENABLE_DEBUGGER +#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.h b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.h index 81311d2a4203..3a5853b0756a 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.h +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/Registration.h @@ -7,7 +7,7 @@ #pragma once -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) #include #include @@ -38,4 +38,4 @@ extern void disableDebugging(DebugSessionToken session); } // namespace facebook::hermes::inspector_modern::chrome -#endif // HERMES_ENABLE_DEBUGGER +#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) diff --git a/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt index 0cf910be5bfd..3a4e6360d3d8 100644 --- a/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt @@ -18,6 +18,10 @@ add_library(bridgeless target_compile_reactnative_options(bridgeless PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(bridgeless PRIVATE -DHERMES_ENABLE_DEBUGGER=1) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(bridgeless PRIVATE -DHERMES_V1_ENABLED=1) + endif() endif () target_include_directories(bridgeless PUBLIC .) diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt index 07b2a26b4422..c5bf10464e08 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt @@ -35,4 +35,8 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) PRIVATE -DHERMES_ENABLE_DEBUGGER=1 ) + + if (DEFINED HERMES_V1_ENABLED) + target_compile_options(bridgelesshermes PRIVATE -DHERMES_V1_ENABLED=1) + endif() endif() diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp b/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp index d09f3a7ee3c5..4e12be7a6a90 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp @@ -14,7 +14,11 @@ #ifdef HERMES_ENABLE_DEBUGGER #include + +#ifndef HERMES_V1_ENABLED #include +#endif + #include #endif @@ -23,7 +27,7 @@ using namespace facebook::jsi; namespace facebook::react { -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) // Wrapper that strongly retains the HermesRuntime for on device debugging. // @@ -90,7 +94,7 @@ class DecoratedRuntime : public jsi::RuntimeDecorator { inspector_modern::chrome::DebugSessionToken debugToken_; }; -#endif +#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) class HermesJSRuntime : public JSRuntime { public: @@ -157,7 +161,7 @@ std::unique_ptr HermesInstance::createJSRuntime( .getPropertyAsObject(*hermesRuntime, "prototype"); errorPrototype.setProperty(*hermesRuntime, "jsEngine", "hermes"); -#ifdef HERMES_ENABLE_DEBUGGER +#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED) auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance(); if (!inspectorFlags.getFuseboxEnabled()) { std::unique_ptr decoratedRuntime = From e9cdc308b4c04753d85757e8877ac00c3c687b95 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 4 Sep 2025 07:42:12 -0700 Subject: [PATCH 0051/1110] Allow to opt-in to use the new Hermes on iOS (#53579) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53579 Changelog: [IOS][ADDED] Added opt-in to use the new Hermes Reviewed By: cipolleschi Differential Revision: D81035113 fbshipit-source-id: b12ca68824ec4e736edd4393a93c28803312eb32 --- .../scripts/cocoapods/jsengine.rb | 3 +- .../react-native/scripts/cocoapods/utils.rb | 10 +++- .../sdks/hermes-engine/hermes-engine.podspec | 27 +++++----- .../sdks/hermes-engine/hermes-utils.rb | 51 +++++++++++++++---- .../utils/build-apple-framework.sh | 8 ++- .../hermes-engine/utils/build-hermes-xcode.sh | 9 +++- .../utils/build-hermesc-xcode.sh | 2 +- 7 files changed, 82 insertions(+), 28 deletions(-) diff --git a/packages/react-native/scripts/cocoapods/jsengine.rb b/packages/react-native/scripts/cocoapods/jsengine.rb index 69508dcbeaf8..a15c92897bb0 100644 --- a/packages/react-native/scripts/cocoapods/jsengine.rb +++ b/packages/react-native/scripts/cocoapods/jsengine.rb @@ -13,7 +13,8 @@ def setup_hermes!(react_native_path: "../node_modules/react-native") react_native_dir = Pod::Config.instance.installation_root.join(react_native_path) # This `:tag => hermestag` below is only to tell CocoaPods to update hermes-engine when React Native version changes. # We have custom logic to compute the source for hermes-engine. See sdks/hermes-engine/* - hermestag_file = File.join(react_native_dir, "sdks", ".hermesversion") + hermestag_file_name = ENV['RCT_HERMES_V1_ENABLED'] == "1" ? ".hermesv1version" : ".hermesversion" + hermestag_file = File.join(react_native_dir, "sdks", hermestag_file_name) hermestag = File.exist?(hermestag_file) ? File.read(hermestag_file).strip : '' pod 'hermes-engine', :podspec => "#{react_native_path}/sdks/hermes-engine/hermes-engine.podspec", :tag => hermestag pod 'React-hermes', :path => "#{react_native_path}/ReactCommon/hermes" diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index 24c7ffd0ce51..4d4872768b71 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -45,9 +45,15 @@ def self.has_pod(installer, name) end def self.set_gcc_preprocessor_definition_for_React_hermes(installer) - self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", :debug) + if ENV['RCT_HERMES_V1_ENABLED'] == "1" + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1 HERMES_V1_ENABLED=1", "React-hermes", :debug) + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1 HERMES_V1_ENABLED=1", "React-RuntimeHermes", :debug) + else + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", :debug) + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-RuntimeHermes", :debug) + end + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "hermes-engine", :debug) - self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-RuntimeHermes", :debug) end def self.set_gcc_preprocessor_definition_for_debugger(installer) diff --git a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec index bc3f134c3739..a3a263c9fd1e 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec +++ b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec @@ -20,6 +20,7 @@ end # package.json package = JSON.parse(File.read(File.join(react_native_path, "package.json"))) +# TODO: T231755000 use the Hermes V1 version instead of React Native version version = package['version'] source_type = hermes_source_type(version, react_native_path) @@ -100,24 +101,26 @@ Pod::Spec.new do |spec| ss.header_dir = 'hermes/cdp' end - spec.subspec 'inspector' do |ss| - ss.source_files = '' - ss.public_header_files = 'API/hermes/inspector/*.h' - ss.header_dir = 'hermes/inspector' - end - - spec.subspec 'inspector_chrome' do |ss| - ss.source_files = '' - ss.public_header_files = 'API/hermes/inspector/chrome/*.h' - ss.header_dir = 'hermes/inspector/chrome' - end - spec.subspec 'Public' do |ss| ss.source_files = '' ss.public_header_files = 'public/hermes/Public/*.h' ss.header_dir = 'hermes/Public' end + if ENV['RCT_HERMES_V1_ENABLED'] != "1" + spec.subspec 'inspector' do |ss| + ss.source_files = '' + ss.public_header_files = 'API/hermes/inspector/*.h' + ss.header_dir = 'hermes/inspector' + end + + spec.subspec 'inspector_chrome' do |ss| + ss.source_files = '' + ss.public_header_files = 'API/hermes/inspector/chrome/*.h' + ss.header_dir = 'hermes/inspector/chrome' + end + end + hermesc_path = "${PODS_ROOT}/hermes-engine/build_host_hermesc" if ENV.has_key?('HERMES_OVERRIDE_HERMESC_PATH') && File.exist?(ENV['HERMES_OVERRIDE_HERMESC_PATH']) then diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index ee0edf5dadfb..3a8aedfef591 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -85,6 +85,10 @@ def hermes_commit_envvar_defined() return ENV.has_key?('HERMES_COMMIT') end +def hermes_v1_enabled() + return ENV['RCT_HERMES_V1_ENABLED'] == "1" +end + def force_build_from_tag(react_native_path) return ENV[ENV_BUILD_FROM_SOURCE] === 'true' && File.exist?(hermestag_file(react_native_path)) end @@ -170,13 +174,19 @@ def podspec_source_build_from_github_commit() def podspec_source_build_from_github_tag(react_native_path) tag = File.read(hermestag_file(react_native_path)).strip - hermes_log("Using tag difined in sdks/.hermesversion: #{tag}") + + if hermes_v1_enabled() + hermes_log("Using tag defined in sdks/.hermesv1version: #{tag}") + else + hermes_log("Using tag defined in sdks/.hermesversion: #{tag}") + end return {:git => HERMES_GITHUB_URL, :tag => tag} end def podspec_source_build_from_github_main() - hermes_log("Using the latest commit from main.") - return {:git => HERMES_GITHUB_URL, :commit => `git ls-remote #{HERMES_GITHUB_URL} main | cut -f 1`.strip} + branch = hermes_v1_enabled() ? "250829098.0.0-stable" : "main" + hermes_log("Using the latest commit from #{branch}.") + return {:git => HERMES_GITHUB_URL, :commit => `git ls-remote #{HERMES_GITHUB_URL} #{branch} | cut -f 1`.strip} end def podspec_source_download_prebuild_release_tarball(react_native_path, version) @@ -200,7 +210,11 @@ def artifacts_dir() end def hermestag_file(react_native_path) - return File.join(react_native_path, "sdks", ".hermesversion") + if hermes_v1_enabled() + return File.join(react_native_path, "sdks", ".hermesv1version") + else + return File.join(react_native_path, "sdks", ".hermesversion") + end end def release_tarball_url(version, build_type) @@ -210,10 +224,18 @@ def release_tarball_url(version, build_type) ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != "" ? ENV['ENTERPRISE_REPOSITORY'] : "https://repo1.maven.org/maven2" - namespace = "com/facebook/react" - # Sample url from Maven: - # https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.71.0/react-native-artifacts-0.71.0-hermes-ios-debug.tar.gz - return "#{maven_repo_url}/#{namespace}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-hermes-ios-#{build_type.to_s}.tar.gz" + + if hermes_v1_enabled() + namespace = "com/facebook/hermes" + # Sample url from Maven: + # https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/0.14.0/hermes-ios-0.14.0-debug.tar.gz + return "#{maven_repo_url}/#{namespace}/hermes-ios/#{version}/hermes-ios-#{version}-#{build_type.to_s}.tar.gz" + else + namespace = "com/facebook/react" + # Sample url from Maven: + # https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.71.0/react-native-artifacts-0.71.0-hermes-ios-debug.tar.gz + return "#{maven_repo_url}/#{namespace}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-hermes-ios-#{build_type.to_s}.tar.gz" + end end def download_stable_hermes(react_native_path, version, configuration) @@ -235,9 +257,18 @@ def download_hermes_tarball(react_native_path, tarball_url, version, configurati end def nightly_tarball_url(version) + # TODO: T231755027 update coordinates and versioning artifact_coordinate = "react-native-artifacts" artifact_name = "hermes-ios-debug.tar.gz" - xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artifact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml" + namespace = "com/facebook/react" + + if hermes_v1_enabled() + artifact_coordinate = "hermes-ios" + artifact_name = "hermes-ios-debug.tar.gz" + namespace = "com/facebook/hermes" + end + + xml_url = "https://central.sonatype.com/repository/maven-snapshots/#{namespace}/#{artifact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml" response = Net::HTTP.get_response(URI(xml_url)) if response.is_a?(Net::HTTPSuccess) @@ -245,7 +276,7 @@ def nightly_tarball_url(version) timestamp = xml.elements['metadata/versioning/snapshot/timestamp'].text build_number = xml.elements['metadata/versioning/snapshot/buildNumber'].text full_version = "#{version}-#{timestamp}-#{build_number}" - final_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artifact_coordinate}/#{version}-SNAPSHOT/#{artifact_coordinate}-#{full_version}-#{artifact_name}" + final_url = "https://central.sonatype.com/repository/maven-snapshots/#{namespace}/#{artifact_coordinate}/#{version}-SNAPSHOT/#{artifact_coordinate}-#{full_version}-#{artifact_name}" return final_url else diff --git a/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh b/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh index f71554e84cff..816ff3ec60cb 100755 --- a/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh +++ b/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh @@ -88,6 +88,11 @@ function configure_apple_framework { xcode_15_flags="LINKER:-ld_classic" fi + boost_context_flag="" + if [[ $1 == "catalyst" ]]; then + boost_context_flag="-DHERMES_ALLOW_BOOST_CONTEXT=0" + fi + pushd "$HERMES_PATH" > /dev/null || exit 1 cmake -S . -B "build_$1" \ -DHERMES_EXTRA_LINKER_FLAGS="$xcode_15_flags" \ @@ -107,7 +112,8 @@ function configure_apple_framework { -DIMPORT_HOST_COMPILERS:PATH="$IMPORT_HOST_COMPILERS_PATH" \ -DJSI_DIR="$JSI_PATH" \ -DHERMES_RELEASE_VERSION="for RN $(get_release_version)" \ - -DCMAKE_BUILD_TYPE="$cmake_build_type" + -DCMAKE_BUILD_TYPE="$cmake_build_type" \ + $boost_context_flag popd > /dev/null || exit 1 } diff --git a/packages/react-native/sdks/hermes-engine/utils/build-hermes-xcode.sh b/packages/react-native/sdks/hermes-engine/utils/build-hermes-xcode.sh index 31132acf0b4e..60506d160e5c 100755 --- a/packages/react-native/sdks/hermes-engine/utils/build-hermes-xcode.sh +++ b/packages/react-native/sdks/hermes-engine/utils/build-hermes-xcode.sh @@ -59,6 +59,11 @@ if [[ $xcode_major_version -ge 15 ]]; then xcode_15_flags="LINKER:-ld_classic" fi +boost_context_flag="" +if [[ $PLATFORM_NAME == "catalyst" ]]; then + boost_context_flag="-DHERMES_ALLOW_BOOST_CONTEXT=0" +fi + architectures=$( echo "$ARCHS" | tr " " ";" ) echo "Configure Apple framework" @@ -83,7 +88,8 @@ echo "Configure Apple framework" -DIMPORT_HOST_COMPILERS:PATH="${hermesc_path}" \ -DJSI_DIR="$jsi_path" \ -DHERMES_RELEASE_VERSION="for RN $release_version" \ - -DCMAKE_BUILD_TYPE="$cmake_build_type" + -DCMAKE_BUILD_TYPE="$cmake_build_type" \ + $boost_context_flag echo "Build Apple framework" @@ -96,6 +102,7 @@ echo "Copy Apple framework to destroot/Library/Frameworks" platform_copy_destination=$(get_platform_copy_destination $PLATFORM_NAME) +mkdir -p "${PODS_ROOT}/hermes-engine/destroot/Library/Frameworks/${platform_copy_destination}" cp -pfR \ "${PODS_ROOT}/hermes-engine/build/${PLATFORM_NAME}/lib/hermesvm.framework" \ "${PODS_ROOT}/hermes-engine/destroot/Library/Frameworks/${platform_copy_destination}" diff --git a/packages/react-native/sdks/hermes-engine/utils/build-hermesc-xcode.sh b/packages/react-native/sdks/hermes-engine/utils/build-hermesc-xcode.sh index 7567ad11aa89..5c75e8e1c611 100755 --- a/packages/react-native/sdks/hermes-engine/utils/build-hermesc-xcode.sh +++ b/packages/react-native/sdks/hermes-engine/utils/build-hermesc-xcode.sh @@ -18,7 +18,7 @@ SDKROOT=$(xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk env -i \ PATH="$PATH" \ SDKROOT="$SDKROOT" \ - "$CMAKE_BINARY" -S "${PODS_ROOT}/hermes-engine" -B "$hermesc_dir_path" -DJSI_DIR="$jsi_path" + "$CMAKE_BINARY" -S "${PODS_ROOT}/hermes-engine" -B "$hermesc_dir_path" -DJSI_DIR="$jsi_path" -DCMAKE_BUILD_TYPE=Release env -i \ PATH="$PATH" \ From 3e9990f860eb9380837ef431ca02def32c4261ad Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 4 Sep 2025 07:42:12 -0700 Subject: [PATCH 0052/1110] Allow to opt-in to use the new Hermes on Android (#53580) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53580 Changelog: [ANDROID][ADDED] Added opt-in to use the new Hermes Reviewed By: cortinico Differential Revision: D81035114 fbshipit-source-id: d01e44190941d161cf641ec4e03ed487aff18dd8 --- gradle.properties | 3 + .../kotlin/com/facebook/react/ReactPlugin.kt | 13 +++- .../react/internal/PrivateReactExtension.kt | 3 + .../facebook/react/utils/DependencyUtils.kt | 73 +++++++++++++------ .../com/facebook/react/utils/ProjectUtils.kt | 9 +++ .../com/facebook/react/utils/PropertyUtils.kt | 12 ++- .../react/utils/DependencyUtilsTest.kt | 33 ++++++--- .../ReactAndroid/build.gradle.kts | 7 ++ .../ReactAndroid/gradle.properties | 2 + .../hermes-engine/build.gradle.kts | 35 +++++++-- .../ReactAndroid/src/main/jni/CMakeLists.txt | 2 + scripts/releases/set-rn-artifacts-version.js | 2 + 12 files changed, 149 insertions(+), 45 deletions(-) diff --git a/gradle.properties b/gradle.properties index 12a6bcebf8b8..e59a251afa6c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,3 +12,6 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # Controls whether to use Hermes from nightly builds. This will speed up builds # but should NOT be turned on for CI or release builds. react.internal.useHermesNightly=false + +# Controls whether to use Hermes 1.0. Clean and rebuild when changing. +hermesV1Enabled=false diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 9f0b5a973486..b09dc85a52ce 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -28,6 +28,7 @@ import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains import com.facebook.react.utils.JsonUtils import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk +import com.facebook.react.utils.ProjectUtils.isHermesV1Enabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson import com.facebook.react.utils.findPackageJsonFile import java.io.File @@ -54,6 +55,10 @@ class ReactPlugin : Plugin { project, ) + if (project.rootProject.isHermesV1Enabled != rootExtension.hermesV1Enabled.get()) { + rootExtension.hermesV1Enabled.set(project.rootProject.isHermesV1Enabled) + } + // App Only Configuration project.pluginManager.withPlugin("com.android.application") { // We wire the root extension with the values coming from the app (either user populated or @@ -67,9 +72,11 @@ class ReactPlugin : Plugin { val reactNativeDir = extension.reactNativeDir.get().asFile val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties") val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile) - val versionString = versionAndGroupStrings.first - val groupString = versionAndGroupStrings.second - configureDependencies(project, versionString, groupString) + val hermesV1Enabled = + if (project.rootProject.hasProperty("hermesV1Enabled")) + project.rootProject.findProperty("hermesV1Enabled") == "true" + else false + configureDependencies(project, versionAndGroupStrings, hermesV1Enabled) configureRepositories(project) } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt index 0c993f59c607..2285d3fac00d 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt @@ -11,6 +11,7 @@ import javax.inject.Inject import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property /** * A private extension we set on the rootProject to make easier to share values at execution time @@ -57,4 +58,6 @@ abstract class PrivateReactExtension @Inject constructor(project: Project) { val codegenDir: DirectoryProperty = objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen")) + + val hermesV1Enabled: Property = objects.property(Boolean::class.java).convention(false) } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt index a5cfb3511577..7fc8e1cfc7b4 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt @@ -7,12 +7,15 @@ package com.facebook.react.utils -import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_PUBLISHING_GROUP +import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP +import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP import com.facebook.react.utils.PropertyUtils.EXCLUSIVE_ENTEPRISE_REPOSITORY import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY_DEFAULT -import com.facebook.react.utils.PropertyUtils.INTERNAL_PUBLISHING_GROUP +import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_PUBLISHING_GROUP +import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_VERSION_NAME import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO +import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_PUBLISHING_GROUP import com.facebook.react.utils.PropertyUtils.INTERNAL_USE_HERMES_NIGHTLY import com.facebook.react.utils.PropertyUtils.INTERNAL_VERSION_NAME import com.facebook.react.utils.PropertyUtils.SCOPED_EXCLUSIVE_ENTEPRISE_REPOSITORY @@ -25,6 +28,13 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository internal object DependencyUtils { + internal data class Coordinates( + val versionString: String, + val hermesVersionString: String, + val reactGroupString: String = DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP, + val hermesGroupString: String = DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP, + ) + /** * This method takes care of configuring the repositories{} block for both the app and all the 3rd * party libraries which are auto-linked. @@ -95,14 +105,15 @@ internal object DependencyUtils { * This method takes care of configuring the resolution strategy for both the app and all the 3rd * party libraries which are auto-linked. Specifically it takes care of: * - Forcing the react-android/hermes-android version to the one specified in the package.json - * - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`. + * - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android` + * - Selecting between the classic Hermes and Hermes V1 */ fun configureDependencies( project: Project, - versionString: String, - groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP, + coordinates: Coordinates, + hermesV1Enabled: Boolean = false, ) { - if (versionString.isBlank()) return + if (coordinates.versionString.isBlank() || coordinates.hermesVersionString.isBlank()) return project.rootProject.allprojects { eachProject -> eachProject.configurations.all { configuration -> // Here we set a dependencySubstitution for both react-native and hermes-engine as those @@ -110,53 +121,61 @@ internal object DependencyUtils { // This allows users to import libraries that are still using // implementation("com.facebook.react:react-native:+") and resolve the right dependency. configuration.resolutionStrategy.dependencySubstitution { - getDependencySubstitutions(versionString, groupString).forEach { (module, dest, reason) -> + getDependencySubstitutions(coordinates, hermesV1Enabled).forEach { (module, dest, reason) + -> it.substitute(it.module(module)).using(it.module(dest)).because(reason) } } configuration.resolutionStrategy.force( - "${groupString}:react-android:${versionString}", + "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", ) if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) { // Contributors only: The hermes-engine version is forced only if the user has // not opted into using nightlies for local development. - configuration.resolutionStrategy.force("${groupString}:hermes-android:${versionString}") + configuration.resolutionStrategy.force( + "${coordinates.reactGroupString}:hermes-android:${coordinates.versionString}" + ) } } } } internal fun getDependencySubstitutions( - versionString: String, - groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP, + coordinates: Coordinates, + hermesV1Enabled: Boolean = false, ): List> { + // TODO: T231755027 update coordinates and versioning val dependencySubstitution = mutableListOf>() + val hermesVersionString = + if (hermesV1Enabled) + "${coordinates.hermesGroupString}:hermes-android:${coordinates.versionString}" + else "${coordinates.reactGroupString}:hermes-android:${coordinates.versionString}" dependencySubstitution.add( Triple( "com.facebook.react:react-native", - "${groupString}:react-android:${versionString}", + "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210.", ) ) dependencySubstitution.add( Triple( "com.facebook.react:hermes-engine", - "${groupString}:hermes-android:${versionString}", + hermesVersionString, "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210.", ) ) - if (groupString != DEFAULT_INTERNAL_PUBLISHING_GROUP) { + if (coordinates.reactGroupString != DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP) { dependencySubstitution.add( Triple( "com.facebook.react:react-android", - "${groupString}:react-android:${versionString}", + "${coordinates.reactGroupString}:react-android:${coordinates.versionString}", "The react-android dependency was modified to use the correct Maven group.", ) ) dependencySubstitution.add( Triple( "com.facebook.react:hermes-android", - "${groupString}:hermes-android:${versionString}", + hermesVersionString, "The hermes-android dependency was modified to use the correct Maven group.", ) ) @@ -164,10 +183,14 @@ internal object DependencyUtils { return dependencySubstitution } - fun readVersionAndGroupStrings(propertiesFile: File): Pair { + fun readVersionAndGroupStrings(propertiesFile: File): Coordinates { val reactAndroidProperties = Properties() propertiesFile.inputStream().use { reactAndroidProperties.load(it) } val versionStringFromFile = (reactAndroidProperties[INTERNAL_VERSION_NAME] as? String).orEmpty() + // TODO: T231755027 update HERMES_VERSION_NAME in gradle.properties to point to the correct + // hermes version + val hermesVersionStringFromFile = + (reactAndroidProperties[INTERNAL_HERMES_VERSION_NAME] as? String).orEmpty() // If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype. val versionString = if (versionStringFromFile.startsWith("0.0.0") || "-nightly-" in versionStringFromFile) { @@ -176,10 +199,18 @@ internal object DependencyUtils { versionStringFromFile } // Returns Maven group for repos using different group for Maven artifacts - val groupString = - reactAndroidProperties[INTERNAL_PUBLISHING_GROUP] as? String - ?: DEFAULT_INTERNAL_PUBLISHING_GROUP - return Pair(versionString, groupString) + val reactGroupString = + reactAndroidProperties[INTERNAL_REACT_PUBLISHING_GROUP] as? String + ?: DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP + val hermesGroupString = + reactAndroidProperties[INTERNAL_HERMES_PUBLISHING_GROUP] as? String + ?: DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP + return Coordinates( + versionString, + hermesVersionStringFromFile, + reactGroupString, + hermesGroupString, + ) } fun Project.mavenRepoFromUrl( diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt index 6558f28ec2d1..9e2aaeff9d86 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt @@ -13,9 +13,11 @@ import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED +import com.facebook.react.utils.PropertyUtils.HERMES_V1_ENABLED import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED +import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_V1_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC import com.facebook.react.utils.PropertyUtils.USE_THIRD_PARTY_JSC @@ -68,6 +70,13 @@ internal object ProjectUtils { (project.hasProperty(SCOPED_USE_THIRD_PARTY_JSC) && project.property(SCOPED_USE_THIRD_PARTY_JSC).toString().toBoolean()) + internal val Project.isHermesV1Enabled: Boolean + get() = + (project.hasProperty(HERMES_V1_ENABLED) && + project.property(HERMES_V1_ENABLED).toString().toBoolean()) || + (project.hasProperty(SCOPED_HERMES_V1_ENABLED) && + project.property(SCOPED_HERMES_V1_ENABLED).toString().toBoolean()) + internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean { val parsedPackageJson = readPackageJsonFile(this, rootProperty) return needsCodegenFromPackageJson(parsedPackageJson) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt index 0253c23e8d4d..f2d89ee0c82e 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt @@ -18,6 +18,10 @@ object PropertyUtils { const val HERMES_ENABLED = "hermesEnabled" const val SCOPED_HERMES_ENABLED = "react.hermesEnabled" + /** Public property that toggles Hermes V1 */ + const val HERMES_V1_ENABLED = "hermesV1Enabled" + const val SCOPED_HERMES_V1_ENABLED = "react.hermesV1Enabled" + /** Public property that toggles edge-to-edge */ const val EDGE_TO_EDGE_ENABLED = "edgeToEdgeEnabled" const val SCOPED_EDGE_TO_EDGE_ENABLED = "react.edgeToEdgeEnabled" @@ -68,9 +72,13 @@ object PropertyUtils { const val INTERNAL_USE_HERMES_NIGHTLY = "react.internal.useHermesNightly" /** Internal property used to override the publishing group for the React Native artifacts. */ - const val INTERNAL_PUBLISHING_GROUP = "react.internal.publishingGroup" - const val DEFAULT_INTERNAL_PUBLISHING_GROUP = "com.facebook.react" + const val INTERNAL_REACT_PUBLISHING_GROUP = "react.internal.publishingGroup" + const val INTERNAL_HERMES_PUBLISHING_GROUP = "react.internal.hermesPublishingGroup" + const val DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP = "com.facebook.react" + const val DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP = "com.facebook.hermes" /** Internal property used to control the version name of React Native */ const val INTERNAL_VERSION_NAME = "VERSION_NAME" + /** Internal property used to control the version name of Hermes Engine */ + const val INTERNAL_HERMES_VERSION_NAME = "HERMES_VERSION_NAME" } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt index 42155fb5c32b..f45435a23864 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt @@ -271,11 +271,13 @@ class DependencyUtilsTest { .isEqualTo(2) } + // TODO: T236767053 + @Test fun configureDependencies_withEmptyVersion_doesNothing() { val project = createProject() - configureDependencies(project, "") + configureDependencies(project, DependencyUtils.Coordinates("", "")) assertThat(project.configurations.first().resolutionStrategy.forcedModules.isEmpty()).isTrue() } @@ -284,7 +286,7 @@ class DependencyUtilsTest { fun configureDependencies_withVersionString_appliesResolutionStrategy() { val project = createProject() - configureDependencies(project, "1.2.3") + configureDependencies(project, DependencyUtils.Coordinates("1.2.3", "1.2.3")) val forcedModules = project.configurations.first().resolutionStrategy.forcedModules assertThat(forcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) @@ -301,7 +303,7 @@ class DependencyUtilsTest { appProject.plugins.apply("com.android.application") libProject.plugins.apply("com.android.library") - configureDependencies(appProject, "1.2.3") + configureDependencies(appProject, DependencyUtils.Coordinates("1.2.3", "1.2.3")) val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules @@ -323,7 +325,10 @@ class DependencyUtilsTest { appProject.plugins.apply("com.android.application") libProject.plugins.apply("com.android.library") - configureDependencies(appProject, "1.2.3", "io.github.test") + configureDependencies( + appProject, + DependencyUtils.Coordinates("1.2.3", "1.2.3", "io.github.test"), + ) val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules @@ -339,7 +344,8 @@ class DependencyUtilsTest { @Test fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly() { - val dependencySubstitutions = getDependencySubstitutions("0.42.0") + val dependencySubstitutions = + getDependencySubstitutions(DependencyUtils.Coordinates("0.42.0", "0.42.0")) assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first) assertThat("com.facebook.react:react-android:0.42.0") @@ -359,7 +365,10 @@ class DependencyUtilsTest { @Test fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly() { - val dependencySubstitutions = getDependencySubstitutions("0.42.0", "io.github.test") + val dependencySubstitutions = + getDependencySubstitutions( + DependencyUtils.Coordinates("0.42.0", "0.42.0", "io.github.test") + ) assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first) assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[0].second) @@ -396,7 +405,7 @@ class DependencyUtilsTest { ) } - val versionString = readVersionAndGroupStrings(propertiesFile).first + val versionString = readVersionAndGroupStrings(propertiesFile).versionString assertThat(versionString).isEqualTo("1000.0.0") } @@ -414,7 +423,7 @@ class DependencyUtilsTest { ) } - val versionString = readVersionAndGroupStrings(propertiesFile).first + val versionString = readVersionAndGroupStrings(propertiesFile).versionString assertThat(versionString).isEqualTo("0.0.0-20221101-2019-cfe811ab1-SNAPSHOT") } @@ -431,7 +440,7 @@ class DependencyUtilsTest { ) } - val versionString = readVersionAndGroupStrings(propertiesFile).first + val versionString = readVersionAndGroupStrings(propertiesFile).versionString assertThat(versionString).isEqualTo("") } @@ -448,7 +457,7 @@ class DependencyUtilsTest { ) } - val versionString = readVersionAndGroupStrings(propertiesFile).first + val versionString = readVersionAndGroupStrings(propertiesFile).versionString assertThat(versionString).isEqualTo("") } @@ -465,7 +474,7 @@ class DependencyUtilsTest { ) } - val groupString = readVersionAndGroupStrings(propertiesFile).second + val groupString = readVersionAndGroupStrings(propertiesFile).reactGroupString assertThat(groupString).isEqualTo("io.github.test") } @@ -482,7 +491,7 @@ class DependencyUtilsTest { ) } - val groupString = readVersionAndGroupStrings(propertiesFile).second + val groupString = readVersionAndGroupStrings(propertiesFile).reactGroupString assertThat(groupString).isEqualTo("com.facebook.react") } diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 32de71a6c920..26a460c033e6 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -39,6 +39,9 @@ val downloadsDir = val thirdPartyNdkDir = File("$buildDir/third-party-ndk") val reactNativeRootDir = projectDir.parent +val hermesV1Enabled = + rootProject.extensions.getByType(PrivateReactExtension::class.java).hermesV1Enabled.get() + // We put the publishing version from gradle.properties inside ext. so other // subprojects can access it as well. extra["publishing_version"] = project.findProperty("VERSION_NAME")?.toString()!! @@ -565,6 +568,10 @@ android { "-DCMAKE_POLICY_DEFAULT_CMP0069=NEW", ) + if (hermesV1Enabled) { + arguments("-DHERMES_V1_ENABLED=1") + } + targets( "reactnative", "jsi", diff --git a/packages/react-native/ReactAndroid/gradle.properties b/packages/react-native/ReactAndroid/gradle.properties index 7c0c467db7a6..0d5448774db7 100644 --- a/packages/react-native/ReactAndroid/gradle.properties +++ b/packages/react-native/ReactAndroid/gradle.properties @@ -1,5 +1,7 @@ VERSION_NAME=1000.0.0 +HERMES_VERSION_NAME=1000.0.0 react.internal.publishingGroup=com.facebook.react +react.internal.hermesPublishingGroup=com.facebook.hermes android.useAndroidX=true diff --git a/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts b/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts index f6df20e3b88d..6e0b447f15ec 100644 --- a/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts +++ b/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import com.facebook.react.internal.PrivateReactExtension import com.facebook.react.tasks.internal.* import de.undercouch.gradle.tasks.download.Download import org.apache.tools.ant.taskdefs.condition.Os @@ -50,6 +51,8 @@ fun getSDKManagerPath(): String { } } +val hermesV1Enabled = + rootProject.extensions.getByType(PrivateReactExtension::class.java).hermesV1Enabled.get() val reactNativeRootDir = project(":packages:react-native:ReactAndroid").projectDir.parent val customDownloadDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR") val downloadsDir = @@ -79,12 +82,21 @@ val hermesBuildOutputFileTree = fileTree(hermesBuildDir.toString()) .include("**/*.cmake", "**/*.marks", "**/compiler_depends.ts", "**/Makefile", "**/link.txt") -var hermesVersion = "main" -val hermesVersionFile = File(reactNativeRootDir, "sdks/.hermesversion") +val hermesVersionProvider: Provider = + providers.provider { + var hermesVersion = if (hermesV1Enabled) "250829098.0.0-stable" else "main" + val hermesVersionFile = + File( + reactNativeRootDir, + if (hermesV1Enabled) "sdks/.hermesv1version" else "\"sdks/.hermesversion\"", + ) -if (hermesVersionFile.exists()) { - hermesVersion = hermesVersionFile.readText() -} + if (hermesVersionFile.exists()) { + hermesVersion = hermesVersionFile.readText() + } + + hermesVersion + } val ndkBuildJobs = Runtime.getRuntime().availableProcessors().toString() val prefabHeadersDir = File("$buildDir/prefab-headers") @@ -95,7 +107,11 @@ val jsiDir = File(reactNativeRootDir, "ReactCommon/jsi") val downloadHermesDest = File(downloadsDir, "hermes.tar.gz") val downloadHermes by tasks.registering(Download::class) { - src("https://github.com/facebook/hermes/tarball/${hermesVersion}") + src( + providers.provider { + "https://github.com/facebook/hermes/tarball/${hermesVersionProvider.get()}" + } + ) onlyIfModified(true) overwrite(true) quiet(true) @@ -151,6 +167,7 @@ val configureBuildForHermes by "-B", hermesBuildDir.toString(), "-DJSI_DIR=" + jsiDir.absolutePath, + "-DCMAKE_BUILD_TYPE=Release", ) if (Os.isFamily(Os.FAMILY_WINDOWS)) { cmakeCommandLine = cmakeCommandLine + "-GNMake Makefiles" @@ -295,7 +312,11 @@ android { // Therefore we're passing as build type Release, to provide a faster build. // This has the (unlucky) side effect of letting AGP call the build // tasks `configureCMakeRelease` while is actually building the debug flavor. - arguments("-DCMAKE_BUILD_TYPE=Release") + arguments( + "-DCMAKE_BUILD_TYPE=Release", + // For debug builds, explicitly enable the Hermes Debugger. + "-DHERMES_ENABLE_DEBUGGER=True", + ) } } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 590236b72484..e28541f17c68 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -22,6 +22,8 @@ file(TO_CMAKE_PATH "${REACT_ANDROID_DIR}" REACT_ANDROID_DIR) file(TO_CMAKE_PATH "${REACT_BUILD_DIR}" REACT_BUILD_DIR) file(TO_CMAKE_PATH "${REACT_COMMON_DIR}" REACT_COMMON_DIR) +set(HERMES_V1_ENABLED OFF CACHE BOOL "Build with support for Hermes v1") + # If you have ccache installed, we're going to honor it. find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) diff --git a/scripts/releases/set-rn-artifacts-version.js b/scripts/releases/set-rn-artifacts-version.js index bf8671237180..9341d5951062 100755 --- a/scripts/releases/set-rn-artifacts-version.js +++ b/scripts/releases/set-rn-artifacts-version.js @@ -155,6 +155,8 @@ function updateTestFiles( async function updateGradleFile(version /*: string */) /*: Promise */ { const contents = await fs.readFile(GRADLE_FILE_PATH, 'utf-8'); + // TODO: T231755027 set HERMES_VERSION_NAME + return fs.writeFile( GRADLE_FILE_PATH, contents.replace(/^VERSION_NAME=.*/, `VERSION_NAME=${version}`), From 2e0bd13a2533fe7ab64125a95b9215b806018c6e Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 4 Sep 2025 07:42:12 -0700 Subject: [PATCH 0053/1110] Use hermesc from node_modules when consuming prebuilt hermes (#53581) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53581 Changelog: [General][Changed] - Changed the source of hermesc binary to be an npm package Reviewed By: cipolleschi, cortinico Differential Revision: D81224001 fbshipit-source-id: 552d0e66fb891974d7b688bfc0bec95e19345d86 --- .../facebook/react/tasks/BundleHermesCTask.kt | 7 ++++++- .../com/facebook/react/utils/PathUtils.kt | 19 ++++++++++++++----- .../com/facebook/react/utils/PathUtilsTest.kt | 10 ++++++++++ .../sdks/hermes-engine/hermes-engine.podspec | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt index 89ba3656a3e6..6cdd4b5986bb 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt @@ -94,7 +94,12 @@ abstract class BundleHermesCTask : DefaultTask() { runCommand(bundleCommand) if (hermesEnabled.get()) { - val detectedHermesCommand = detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get()) + val hermesV1Enabled = + if (project.rootProject.hasProperty("hermesV1Enabled")) + project.rootProject.findProperty("hermesV1Enabled") == "true" + else false + val detectedHermesCommand = + detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get(), hermesV1Enabled) val bytecodeFile = File("${bundleFile}.hbc") val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename) val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt index 45dc97b69070..e8f5a427c56d 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt @@ -122,11 +122,16 @@ private fun detectCliFile(reactNativeRoot: File, preconfiguredCliFile: File?): F * used if the user is building Hermes from source. * 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%` * is substituted with the correct OS arch. This will be used if the user is using a precompiled - * hermes-engine package. + * hermes-engine package. Or, if the user has opted in to use Hermes V1, the used file will be + * located in `node_modules/hermes-compiler/%OS-BIN%/hermesc` where `%OS-BIN%` is substituted + * with the correct OS arch. * 4. Fails otherwise */ -internal fun detectOSAwareHermesCommand(projectRoot: File, hermesCommand: String): String { - // 1. If the project specifies a Hermes command, don't second guess it. +internal fun detectOSAwareHermesCommand( + projectRoot: File, + hermesCommand: String, + hermesV1Enabled: Boolean = false, +): String { // 1. If the project specifies a Hermes command, don't second guess it. if (hermesCommand.isNotBlank()) { val osSpecificHermesCommand = if ("%OS-BIN%" in hermesCommand) { @@ -146,9 +151,12 @@ internal fun detectOSAwareHermesCommand(projectRoot: File, hermesCommand: String return builtHermesc.cliPath(projectRoot) } - // 3. If the react-native contains a pre-built hermesc, use it. + // 3. If Hermes V1 is enabled, use hermes-compiler from npm, otherwise, if the + // react-native contains a pre-built hermesc, use it. + val hermesCPath = if (hermesV1Enabled) HERMES_COMPILER_NPM_DIR else HERMESC_IN_REACT_NATIVE_DIR val prebuiltHermesPath = - HERMESC_IN_REACT_NATIVE_DIR.plus(getHermesCBin()) + hermesCPath + .plus(getHermesCBin()) .replace("%OS-BIN%", getHermesOSBin()) // Execution on Windows fails with / as separator .replace('/', File.separatorChar) @@ -233,6 +241,7 @@ internal fun readPackageJsonFile( return packageJson?.let { JsonUtils.fromPackageJson(it) } } +private const val HERMES_COMPILER_NPM_DIR = "node_modules/hermes-compiler/%OS-BIN%/" private const val HERMESC_IN_REACT_NATIVE_DIR = "node_modules/react-native/sdks/hermesc/%OS-BIN%/" private const val HERMESC_BUILT_FROM_SOURCE_DIR = "node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/bin/" diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt index 5cf0705be9ec..4a12ed750f89 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt @@ -162,6 +162,16 @@ class PathUtilsTest { assertThat(detectOSAwareHermesCommand(tempFolder.root, "")).isEqualTo(expected.toString()) } + @Test + @WithOs(OS.MAC) + fun detectOSAwareHermesCommand_withHermesV1Enabled() { + tempFolder.newFolder("node_modules/hermes-compiler/osx-bin/") + val expected = tempFolder.newFile("node_modules/hermes-compiler/osx-bin//hermesc") + + assertThat(detectOSAwareHermesCommand(tempFolder.root, "", hermesV1Enabled = true)) + .isEqualTo(expected.toString()) + } + @Test(expected = IllegalStateException::class) @WithOs(OS.MAC) fun detectOSAwareHermesCommand_failsIfNotFound() { diff --git a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec index a3a263c9fd1e..f772282f9a3d 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec +++ b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec @@ -63,6 +63,21 @@ Pod::Spec.new do |spec| ss.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermesvm.framework" end + # When using the local prebuilt tarball, it should include hermesc compatible with the used VM. + # In other cases, when using Hermes V1, the prebuilt versioned binaries can be used. + # TODO: T236142916 hermesc should be consumed from NPM even when not using Hermes V1 + if source_type != HermesEngineSourceType::LOCAL_PREBUILT_TARBALL && ENV['RCT_HERMES_V1_ENABLED'] == "1" + hermes_compiler_path = File.dirname(Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "hermes-compiler", + {paths: [process.argv[1]]} + )', __dir__]).strip + ) + + spec.user_target_xcconfig = { + 'HERMES_CLI_PATH' => "#{hermes_compiler_path}/osx-bin/hermesc" + } + end # Right now, even reinstalling pods with the PRODUCTION flag turned on, does not change the version of hermes that is downloaded # To remove the PRODUCTION flag, we want to download the right version of hermes on the flight From 2b4c48ae47b5075bf67709f262fa8c19bdef17a3 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Thu, 4 Sep 2025 08:13:04 -0700 Subject: [PATCH 0054/1110] export Logger type from dev-middleware (#53586) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53586 Changelog: [Internal] Reviewed By: huntie Differential Revision: D81588537 fbshipit-source-id: e6537dd831cfb73ce93326e2e0c0a2bcd3929caa --- packages/dev-middleware/src/index.flow.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev-middleware/src/index.flow.js b/packages/dev-middleware/src/index.flow.js index bb19fa093e23..3963095cdcee 100644 --- a/packages/dev-middleware/src/index.flow.js +++ b/packages/dev-middleware/src/index.flow.js @@ -8,8 +8,6 @@ * @format */ -export {default as createDevMiddleware} from './createDevMiddleware'; - export type { BrowserLauncher, DebuggerShellPreparationResult, @@ -20,5 +18,7 @@ export type { CustomMessageHandlerConnection, CreateCustomMessageHandlerFn, } from './inspector-proxy/CustomMessageHandler'; +export type {Logger} from './types/Logger'; export {default as unstable_DefaultBrowserLauncher} from './utils/DefaultBrowserLauncher'; +export {default as createDevMiddleware} from './createDevMiddleware'; From 0a3b3fcd181adad910b682466b7e9029525c7129 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Thu, 4 Sep 2025 09:47:15 -0700 Subject: [PATCH 0055/1110] Add RN feature flag for Image view recycling (#53600) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53600 # Changelog: [Internal] - Adds the corresponding feature flag, similarly as it's done for other component types. The flag is used in the next diff. Reviewed By: mdvacca Differential Revision: D81681404 fbshipit-source-id: f9f155379034695f5df6cc4f0d3787ff4c69df7f --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 ++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 ++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 80 ++++++++++++------- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 ++- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 11 +++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 20 files changed, 167 insertions(+), 50 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 47a89ef25e6f..2e8ddc6b2978 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<92a6edded037a504fc1c2c8ae88deae1>> */ /** @@ -258,6 +258,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableViewRecycling(): Boolean = accessor.enableViewRecycling() + /** + * Enables View Recycling for via ReactViewGroup/ReactViewManager. + */ + @JvmStatic + public fun enableViewRecyclingForImage(): Boolean = accessor.enableViewRecyclingForImage() + /** * Enables View Recycling for via ReactViewGroup/ReactViewManager. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 0e74d4361295..0b66c832b802 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<37203dffb9421d1036aaeaeaa7319e28>> + * @generated SignedSource<> */ /** @@ -58,6 +58,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableResourceTimingAPICache: Boolean? = null private var enableViewCullingCache: Boolean? = null private var enableViewRecyclingCache: Boolean? = null + private var enableViewRecyclingForImageCache: Boolean? = null private var enableViewRecyclingForScrollViewCache: Boolean? = null private var enableViewRecyclingForTextCache: Boolean? = null private var enableViewRecyclingForViewCache: Boolean? = null @@ -431,6 +432,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableViewRecyclingForImage(): Boolean { + var cached = enableViewRecyclingForImageCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableViewRecyclingForImage() + enableViewRecyclingForImageCache = cached + } + return cached + } + override fun enableViewRecyclingForScrollView(): Boolean { var cached = enableViewRecyclingForScrollViewCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 1702ddfcd052..615bdf546b43 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9c0acc876e3205fe2ea181e71eb512c9>> + * @generated SignedSource<<4410628511f112f3eef22434a05fa757>> */ /** @@ -104,6 +104,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableViewRecycling(): Boolean + @DoNotStrip @JvmStatic public external fun enableViewRecyclingForImage(): Boolean + @DoNotStrip @JvmStatic public external fun enableViewRecyclingForScrollView(): Boolean @DoNotStrip @JvmStatic public external fun enableViewRecyclingForText(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 73b1b91314da..54e65b67538e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<05bfed9fc7131062c8b16246986fc999>> + * @generated SignedSource<<411732d441bacbef37c3474b9041202c>> */ /** @@ -99,6 +99,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableViewRecycling(): Boolean = false + override fun enableViewRecyclingForImage(): Boolean = true + override fun enableViewRecyclingForScrollView(): Boolean = false override fun enableViewRecyclingForText(): Boolean = true diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index e6b98e04e655..d9235b1552a4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9a18369464f81c3d03f2702716dfdb29>> + * @generated SignedSource<<2f65e0e9066a8c2c35c44ea7c73dbedc>> */ /** @@ -62,6 +62,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableResourceTimingAPICache: Boolean? = null private var enableViewCullingCache: Boolean? = null private var enableViewRecyclingCache: Boolean? = null + private var enableViewRecyclingForImageCache: Boolean? = null private var enableViewRecyclingForScrollViewCache: Boolean? = null private var enableViewRecyclingForTextCache: Boolean? = null private var enableViewRecyclingForViewCache: Boolean? = null @@ -473,6 +474,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableViewRecyclingForImage(): Boolean { + var cached = enableViewRecyclingForImageCache + if (cached == null) { + cached = currentProvider.enableViewRecyclingForImage() + accessedFeatureFlags.add("enableViewRecyclingForImage") + enableViewRecyclingForImageCache = cached + } + return cached + } + override fun enableViewRecyclingForScrollView(): Boolean { var cached = enableViewRecyclingForScrollViewCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 56e83f450650..7f661006d780 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<845b2ee5edc9aedbdbd052d9a930f666>> + * @generated SignedSource<> */ /** @@ -99,6 +99,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableViewRecycling(): Boolean + @DoNotStrip public fun enableViewRecyclingForImage(): Boolean + @DoNotStrip public fun enableViewRecyclingForScrollView(): Boolean @DoNotStrip public fun enableViewRecyclingForText(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 7136900e64f0..fd322ea6e133 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -267,6 +267,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableViewRecyclingForImage() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableViewRecyclingForImage"); + return method(javaProvider_); + } + bool enableViewRecyclingForScrollView() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableViewRecyclingForScrollView"); @@ -641,6 +647,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableViewRecycling( return ReactNativeFeatureFlags::enableViewRecycling(); } +bool JReactNativeFeatureFlagsCxxInterop::enableViewRecyclingForImage( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableViewRecyclingForImage(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableViewRecyclingForScrollView( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableViewRecyclingForScrollView(); @@ -936,6 +947,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableViewRecycling", JReactNativeFeatureFlagsCxxInterop::enableViewRecycling), + makeNativeMethod( + "enableViewRecyclingForImage", + JReactNativeFeatureFlagsCxxInterop::enableViewRecyclingForImage), makeNativeMethod( "enableViewRecyclingForScrollView", JReactNativeFeatureFlagsCxxInterop::enableViewRecyclingForScrollView), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 559bf56b3e3b..ace59ba39374 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<31298767c1dd669d2a755e67edacc911>> + * @generated SignedSource<<6a98f8398948f8d56e51d3eff19c5d05>> */ /** @@ -144,6 +144,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableViewRecycling( facebook::jni::alias_ref); + static bool enableViewRecyclingForImage( + facebook::jni::alias_ref); + static bool enableViewRecyclingForScrollView( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index b6eed59ab63a..83c4a3c4136c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9a538d3ebb58173e7e4e34453b71454b>> + * @generated SignedSource<<5bd932f1d52596dad6ea86a2674170ff>> */ /** @@ -178,6 +178,10 @@ bool ReactNativeFeatureFlags::enableViewRecycling() { return getAccessor().enableViewRecycling(); } +bool ReactNativeFeatureFlags::enableViewRecyclingForImage() { + return getAccessor().enableViewRecyclingForImage(); +} + bool ReactNativeFeatureFlags::enableViewRecyclingForScrollView() { return getAccessor().enableViewRecyclingForScrollView(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index be6ac38c3d63..f6a20cb6d7ec 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3321d357fe5c74fa42c2d0b15a744f87>> + * @generated SignedSource<<3cde1e9bcf234515551ba57ecf02e3a7>> */ /** @@ -229,6 +229,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableViewRecycling(); + /** + * Enables View Recycling for via ReactViewGroup/ReactViewManager. + */ + RN_EXPORT static bool enableViewRecyclingForImage(); + /** * Enables View Recycling for via ReactViewGroup/ReactViewManager. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 120375bd4e41..291aee404016 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<041526548ef83b4afb12229a0345e29e>> + * @generated SignedSource<> */ /** @@ -713,6 +713,24 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForImage() { + auto flagValue = enableViewRecyclingForImage_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(38, "enableViewRecyclingForImage"); + + flagValue = currentProvider_->enableViewRecyclingForImage(); + enableViewRecyclingForImage_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { auto flagValue = enableViewRecyclingForScrollView_.load(); @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(39, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableViewRecyclingForText"); + markFlagAsAccessed(40, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableViewRecyclingForView"); + markFlagAsAccessed(41, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableVirtualViewDebugFeatures"); + markFlagAsAccessed(42, "enableVirtualViewDebugFeatures"); flagValue = currentProvider_->enableVirtualViewDebugFeatures(); enableVirtualViewDebugFeatures_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewRenderState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enableVirtualViewRenderState"); + markFlagAsAccessed(43, "enableVirtualViewRenderState"); flagValue = currentProvider_->enableVirtualViewRenderState(); enableVirtualViewRenderState_ = flagValue; @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewWindowFocusDetection() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "enableVirtualViewWindowFocusDetection"); + markFlagAsAccessed(44, "enableVirtualViewWindowFocusDetection"); flagValue = currentProvider_->enableVirtualViewWindowFocusDetection(); enableVirtualViewWindowFocusDetection_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(45, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "fuseboxEnabledRelease"); + markFlagAsAccessed(46, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(47, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(48, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "perfMonitorV2Enabled"); + markFlagAsAccessed(49, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -920,7 +938,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "preparedTextCacheSize"); + markFlagAsAccessed(50, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(51, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(52, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(53, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(54, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(55, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(56, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(57, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useFabricInterop"); + markFlagAsAccessed(58, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(59, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(60, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(61, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(62, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "useRawPropsJsiValue"); + markFlagAsAccessed(63, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "useShadowNodeStateOnClone"); + markFlagAsAccessed(64, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "useTurboModuleInterop"); + markFlagAsAccessed(65, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "useTurboModules"); + markFlagAsAccessed(66, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1226,7 +1244,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "virtualViewHysteresisRatio"); + markFlagAsAccessed(67, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1244,7 +1262,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "virtualViewPrerenderRatio"); + markFlagAsAccessed(68, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 8e8d3c294e4f..a580922d1ec7 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9c51131fae9f19d03050be0c74ca3d4b>> + * @generated SignedSource<> */ /** @@ -70,6 +70,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableResourceTimingAPI(); bool enableViewCulling(); bool enableViewRecycling(); + bool enableViewRecyclingForImage(); bool enableViewRecyclingForScrollView(); bool enableViewRecyclingForText(); bool enableViewRecyclingForView(); @@ -111,7 +112,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 68> accessedFeatureFlags_; + std::array, 69> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -151,6 +152,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableResourceTimingAPI_; std::atomic> enableViewCulling_; std::atomic> enableViewRecycling_; + std::atomic> enableViewRecyclingForImage_; std::atomic> enableViewRecyclingForScrollView_; std::atomic> enableViewRecyclingForText_; std::atomic> enableViewRecyclingForView_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 0c29a6ef23c2..2969a1fc9711 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5f9c3ecf7887653fd5348a05391ab7d0>> + * @generated SignedSource<<6d8adaa4b960407540afb1d6e42a3840>> */ /** @@ -179,6 +179,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableViewRecyclingForImage() override { + return true; + } + bool enableViewRecyclingForScrollView() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 1a373688ac0b..18c52e44c7e5 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<027cef9dd44f14a71be7c0d1b90238b3>> + * @generated SignedSource<> */ /** @@ -387,6 +387,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableViewRecycling(); } + bool enableViewRecyclingForImage() override { + auto value = values_["enableViewRecyclingForImage"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableViewRecyclingForImage(); + } + bool enableViewRecyclingForScrollView() override { auto value = values_["enableViewRecyclingForScrollView"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 837e85c6fd3b..6eb9a1e93b2a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<07b25b19a265e8afe53eeeffade4dfc0>> */ /** @@ -63,6 +63,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableResourceTimingAPI() = 0; virtual bool enableViewCulling() = 0; virtual bool enableViewRecycling() = 0; + virtual bool enableViewRecyclingForImage() = 0; virtual bool enableViewRecyclingForScrollView() = 0; virtual bool enableViewRecyclingForText() = 0; virtual bool enableViewRecyclingForView() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 624afc05f7f9..bd63a24e7483 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<07df05bf452f78603bbf79594aa0cba0>> + * @generated SignedSource<<348005db0ceee698cd7c33f29bcf85a6>> */ /** @@ -234,6 +234,11 @@ bool NativeReactNativeFeatureFlags::enableViewRecycling( return ReactNativeFeatureFlags::enableViewRecycling(); } +bool NativeReactNativeFeatureFlags::enableViewRecyclingForImage( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableViewRecyclingForImage(); +} + bool NativeReactNativeFeatureFlags::enableViewRecyclingForScrollView( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableViewRecyclingForScrollView(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 4b1d9860ee40..b3f455bde8be 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<228d7484ef89bb01fee8dcff2657c56a>> + * @generated SignedSource<> */ /** @@ -112,6 +112,8 @@ class NativeReactNativeFeatureFlags bool enableViewRecycling(jsi::Runtime& runtime); + bool enableViewRecyclingForImage(jsi::Runtime& runtime); + bool enableViewRecyclingForScrollView(jsi::Runtime& runtime); bool enableViewRecyclingForText(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index d2116260b4fb..7ec2687d929b 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -451,6 +451,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableViewRecyclingForImage: { + defaultValue: true, + metadata: { + dateAdded: '2025-09-04', + description: + 'Enables View Recycling for via ReactViewGroup/ReactViewManager.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableViewRecyclingForScrollView: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 08f2f5faaeb9..3ff0dd58d6f3 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7de65fc90fa1275f89375fd214b94172>> + * @generated SignedSource<<2bef8486b596a7593bbc931da63b2682>> * @flow strict * @noformat */ @@ -88,6 +88,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableResourceTimingAPI: Getter, enableViewCulling: Getter, enableViewRecycling: Getter, + enableViewRecyclingForImage: Getter, enableViewRecyclingForScrollView: Getter, enableViewRecyclingForText: Getter, enableViewRecyclingForView: Getter, @@ -351,6 +352,10 @@ export const enableViewCulling: Getter = createNativeFlagGetter('enable * Enables View Recycling. When enabled, individual ViewManagers must still opt-in. */ export const enableViewRecycling: Getter = createNativeFlagGetter('enableViewRecycling', false); +/** + * Enables View Recycling for via ReactViewGroup/ReactViewManager. + */ +export const enableViewRecyclingForImage: Getter = createNativeFlagGetter('enableViewRecyclingForImage', true); /** * Enables View Recycling for via ReactViewGroup/ReactViewManager. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index ef29da894136..b874e9013d3b 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1e6f17de0ebb06a085d4cc563df1e32b>> + * @generated SignedSource<<1c61b3b4390b75dab658518201379081>> * @flow strict * @noformat */ @@ -63,6 +63,7 @@ export interface Spec extends TurboModule { +enableResourceTimingAPI?: () => boolean; +enableViewCulling?: () => boolean; +enableViewRecycling?: () => boolean; + +enableViewRecyclingForImage?: () => boolean; +enableViewRecyclingForScrollView?: () => boolean; +enableViewRecyclingForText?: () => boolean; +enableViewRecyclingForView?: () => boolean; From 47f32ffae07905543bb83ca60c0f46400a8e5134 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Thu, 4 Sep 2025 10:00:18 -0700 Subject: [PATCH 0056/1110] add comments regarding RCTPackagerConnection's reconnect (#53558) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53558 Changelog: [Internal] Got confused regarding why "reconnect" does not actually trigger a reconnect. It turns out, it only triggers a reconnect if the URL has changed. Reviewed By: cipolleschi, huntie Differential Revision: D80629308 fbshipit-source-id: 098ef5e91f3748deb9bc707b79bc0395d2442ca4 --- packages/react-native/React/DevSupport/RCTPackagerConnection.h | 2 +- packages/react-native/React/DevSupport/RCTPackagerConnection.mm | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/DevSupport/RCTPackagerConnection.h b/packages/react-native/React/DevSupport/RCTPackagerConnection.h index 014f73f97933..cdd7c745a895 100644 --- a/packages/react-native/React/DevSupport/RCTPackagerConnection.h +++ b/packages/react-native/React/DevSupport/RCTPackagerConnection.h @@ -59,7 +59,7 @@ typedef void (^RCTConnectedHandler)(void); /** Disconnects and removes all handlers. */ - (void)stop; -/** Reconnect with given packager server. */ +/** Reconnect with given packager server, if packagerServerHostPort has changed. */ - (void)reconnect:(NSString *)packagerServerHostPort; /** diff --git a/packages/react-native/React/DevSupport/RCTPackagerConnection.mm b/packages/react-native/React/DevSupport/RCTPackagerConnection.mm index 39419d60e120..95dbf82437c9 100644 --- a/packages/react-native/React/DevSupport/RCTPackagerConnection.mm +++ b/packages/react-native/React/DevSupport/RCTPackagerConnection.mm @@ -160,6 +160,7 @@ - (void)reconnect:(NSString *)packagerServerHostPort - (void)bundleURLSettingsChanged { + // Will only reconnect if `packagerServerHostPort` has actually changed [self reconnect:[[RCTBundleURLProvider sharedSettings] packagerServerHostPort]]; } From 1f57ae5249796f31ab10d42280e1fa82adc76235 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Thu, 4 Sep 2025 11:25:39 -0700 Subject: [PATCH 0057/1110] Distribute React Native DevTools binaries via GitHub Releases (#52930) Summary: bypass-github-export-checks OSS release infrastructure for the (experimental) React Native DevTools standalone shell. Currently, binaries are built continuously on Meta infra and served from the Meta CDN using fbcdn.net URLs checked into a DotSlash file in the repo, e.g.: https://github.com/facebook/react-native/blob/15373218ec572c0e43325845b80a849ad5174cc3/packages/debugger-shell/bin/react-native-devtools#L9-L18 For open source releases we want to primarily distribute the binaries as GitHub release assets, while keeping the Meta CDN URLs as a secondary option. This PR makes the necessary changes to the release workflows to support this: * `workflows/create-release.yml` (modified): As part of the release commit, rewrite the DotSlash file to include the release asset URLs. * **NOTE:** After this commit, **the new URLs don't work yet**, because they refer to a release that hasn't been published. Despite this, the DotSlash file remains valid and usable (because DotSlash will happily fall back to the Meta CDN URLs, which are still in the file). * `workflows/create-draft-release.yml` (modified): After creating a draft release, fetch the binaries from the Meta CDN and reupload them to GitHub as release assets. This is based on the contents of the DotSlash file rewritten by `create-release.yml`. * `workflows/validate-dotslash-artifacts.yml` (new): After the release is published, all URLs referenced by the DotSlash (both Meta CDN URL and GH release asset URLs) should be valid and refer to the same artifacts. This workflow checks that this is the case. * If this workflow fails on a published release, the release may need to be burned or a hotfix release may be necessary - as the release will stop working correctly once the Meta CDN stops serving the assets. * This workflow will also be running continuously on `main`. If it fails on a commit in `main`, there might be a connectivity issue between the GHA runner and the Meta CDN, or there might be an issue on the Meta side. NOTE: These changes to the release pipeline are generic and reusable; if we later add another DotSlash-based tool whose binaries need to be mirrored as GitHub release assets, we just need to add it to the `FIRST_PARTY_DOTSLASH_FILES` array. ## Changelog: [Internal] Mirror React Native DevTools binaries in GitHub Releases Pull Request resolved: https://github.com/facebook/react-native/pull/52930 Test Plan: ### Step 0: Unit tests I've added unit tests for `dotslash-utils`, `curl-utils`, and for the majority of the logic that makes up the new release scripts (`write-dotslash-release-assets-urls`, `upload-release-assets-for-dotslash`, `validate-dotslash-artifacts`). ### Step 1: Test release commit Created a test branch and draft PR: https://github.com/facebook/react-native/pull/53147. Locally created a release commit, simulating the create-release GH workflow: ``` node scripts/releases/create-release-commit.js --reactNativeVersion 0.82.0-20250903-0830 --no-dry-run ``` This updated the DotSlash file in the branch: https://github.com/facebook/react-native/pull/53147/commits/2deeb7e70376ee80b99f27bea4825789f22a89a3#diff-205a9ff6005e30be061eaa64b9cb50b15b0e909dd188e0866189e952655a3483 NOTE: I've also ensured that the `create-release-commit` script correctly updates the DotSlash file when running from a branch that already has a release commit - see screenshot: image ### Step 2: Test draft release Enabled testing the create-draft-release GH workflow in the test branch using these temporary hacks: * https://github.com/facebook/react-native/pull/53147/commits/81f334eac5147d4dbf5f6d7d627ddfa52cd197be * https://github.com/facebook/react-native/pull/53147/commits/6d8851657629de7e0b710ed8f5dd7d0f7b9847cc * https://github.com/facebook/react-native/pull/53147/commits/1428a8da8b9fb29c45fc33d79f311dd1fe273433 Workflow run: https://github.com/facebook/react-native/actions/runs/17426711373/job/49475327346 Draft release: https://github.com/facebook/react-native/releases/tag/untagged-c6a62a58e5baa37936e1 Draft release screenshot for posterity (since we'll likely delete the draft release after landing this): image ### Step 3: Test post-release validation script For obvious reasons, I've avoided actually publishing the above draft release. But I have run the `validate-dotslash-artifacts` workflow on the *current* branch to ensure that the logic is correct: https://github.com/motiz88/react-native/actions/runs/17426885205/job/49475888486 Running `node scripts/releases/validate-dotslash-artifacts.js` in the release branch (without publishing the release first) fails, as expected: image ## Next steps This PR is all the infra needed ahead of the 0.82 ~~branch cut~~ infra freeze to support the React Native DevTools standalone shell, at least on the GitHub side. ~~Some minor infra work remains on the Meta side, plus some product/logic changes to the React Native DevTools standalone shell that I'm intending to finish in time for 0.82 (for an experimental rollout).~~ EDIT: All the planned work has landed; the feature is code-complete on `main` as well as in `0.82-stable` (apart from this infra change). As a one-off, once we've actually published 0.82.0-rc.1, we'll want to have a human look at the published artifacts and CI workflow logs to ensure everything is in order. (I'll make sure to communicate this to the 0.82 release crew.) Afterwards, the automation added in this PR should be sufficient. Reviewed By: huntie Differential Revision: D81578704 Pulled By: motiz88 fbshipit-source-id: 6a4a48c3713221a89dd5fc88851674c1ddc6bb10 --- .../__tests__/createDraftRelease-test.js | 18 +- .../workflow-scripts/createDraftRelease.js | 9 +- .github/workflows/create-draft-release.yml | 17 +- .../workflows/validate-dotslash-artifacts.yml | 48 ++ flow-typed/npm/@expo/spawn-async_v1.x.x.js | 46 ++ flow-typed/npm/@octokit/rest_v22.x.x.js | 61 +++ flow-typed/npm/fb-dotslash_v0.x.x.js | 13 + flow-typed/npm/jsonc-parser_v2.2.x.js | 421 ++++++++++++++++++ package.json | 5 + .../debugger-shell/src/node/index.flow.js | 6 +- .../src/node/private/LaunchUtils.js | 10 +- ...d-release-assets-for-dotslash-test.js.snap | 203 +++++++++ ...e-dotslash-release-asset-urls-test.js.snap | 128 ++++++ scripts/releases/__tests__/snapshot-utils.js | 92 ++++ ...upload-release-assets-for-dotslash-test.js | 351 +++++++++++++++ .../write-dotslash-release-asset-urls-test.js | 203 +++++++++ scripts/releases/create-release-commit.js | 6 + .../upload-release-assets-for-dotslash.js | 395 ++++++++++++++++ .../__snapshots__/dotslash-utils-test.js.snap | 102 +++++ .../utils/__tests__/curl-utils-test.js | 60 +++ .../utils/__tests__/dotslash-utils-test.js | 267 +++++++++++ scripts/releases/utils/curl-utils.js | 73 +++ scripts/releases/utils/dotslash-utils.js | 214 +++++++++ scripts/releases/utils/octokit-utils.js | 58 +++ .../releases/validate-dotslash-artifacts.js | 99 ++++ .../write-dotslash-release-asset-urls.js | 171 +++++++ yarn.lock | 161 ++++++- 27 files changed, 3215 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/validate-dotslash-artifacts.yml create mode 100644 flow-typed/npm/@expo/spawn-async_v1.x.x.js create mode 100644 flow-typed/npm/@octokit/rest_v22.x.x.js create mode 100644 flow-typed/npm/fb-dotslash_v0.x.x.js create mode 100644 flow-typed/npm/jsonc-parser_v2.2.x.js create mode 100644 scripts/releases/__tests__/__snapshots__/upload-release-assets-for-dotslash-test.js.snap create mode 100644 scripts/releases/__tests__/__snapshots__/write-dotslash-release-asset-urls-test.js.snap create mode 100644 scripts/releases/__tests__/snapshot-utils.js create mode 100644 scripts/releases/__tests__/upload-release-assets-for-dotslash-test.js create mode 100644 scripts/releases/__tests__/write-dotslash-release-asset-urls-test.js create mode 100644 scripts/releases/upload-release-assets-for-dotslash.js create mode 100644 scripts/releases/utils/__tests__/__snapshots__/dotslash-utils-test.js.snap create mode 100644 scripts/releases/utils/__tests__/curl-utils-test.js create mode 100644 scripts/releases/utils/__tests__/dotslash-utils-test.js create mode 100644 scripts/releases/utils/curl-utils.js create mode 100644 scripts/releases/utils/dotslash-utils.js create mode 100644 scripts/releases/utils/octokit-utils.js create mode 100644 scripts/releases/validate-dotslash-artifacts.js create mode 100644 scripts/releases/write-dotslash-release-asset-urls.js diff --git a/.github/workflow-scripts/__tests__/createDraftRelease-test.js b/.github/workflow-scripts/__tests__/createDraftRelease-test.js index 77901d4df099..587e48e6d320 100644 --- a/.github/workflow-scripts/__tests__/createDraftRelease-test.js +++ b/.github/workflow-scripts/__tests__/createDraftRelease-test.js @@ -188,6 +188,7 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/ status: 201, json: () => Promise.resolve({ + id: 1, html_url: 'https://github.com/facebook/react-native/releases/tag/v0.77.1', }), @@ -208,9 +209,11 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/ body: fetchBody, }, ); - expect(response).toEqual( - 'https://github.com/facebook/react-native/releases/tag/v0.77.1', - ); + expect(response).toEqual({ + id: 1, + html_url: + 'https://github.com/facebook/react-native/releases/tag/v0.77.1', + }); }); it('creates a draft release for prerelease on GitHub', async () => { @@ -238,6 +241,7 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/ status: 201, json: () => Promise.resolve({ + id: 1, html_url: 'https://github.com/facebook/react-native/releases/tag/v0.77.1', }), @@ -258,9 +262,11 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/ body: fetchBody, }, ); - expect(response).toEqual( - 'https://github.com/facebook/react-native/releases/tag/v0.77.1', - ); + expect(response).toEqual({ + id: 1, + html_url: + 'https://github.com/facebook/react-native/releases/tag/v0.77.1', + }); }); it('throws if the post failes', async () => { diff --git a/.github/workflow-scripts/createDraftRelease.js b/.github/workflow-scripts/createDraftRelease.js index f8737c3cbfda..7b7692ce2975 100644 --- a/.github/workflow-scripts/createDraftRelease.js +++ b/.github/workflow-scripts/createDraftRelease.js @@ -101,7 +101,11 @@ async function _createDraftReleaseOnGitHub(version, body, latest, token) { } const data = await response.json(); - return data.html_url; + const {html_url, id} = data; + return { + html_url, + id, + }; } function moveToChangelogBranch(version) { @@ -124,7 +128,8 @@ async function createDraftRelease(version, latest, token) { latest, token, ); - log(`Created draft release: ${release}`); + log(`Created draft release: ${release.html_url}, ID ${release.id}`); + return release; } module.exports = { diff --git a/.github/workflows/create-draft-release.yml b/.github/workflows/create-draft-release.yml index d3b89dba90d8..5852b8fec710 100644 --- a/.github/workflows/create-draft-release.yml +++ b/.github/workflows/create-draft-release.yml @@ -21,9 +21,24 @@ jobs: git config --local user.name "React Native Bot" - name: Create draft release uses: actions/github-script@v6 + id: create-draft-release with: script: | const {createDraftRelease} = require('./.github/workflow-scripts/createDraftRelease.js'); const version = '${{ github.ref_name }}'; const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js'); - await createDraftRelease(version, isLatest(), '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}'); + return (await createDraftRelease(version, isLatest(), '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}')).id; + result-encoding: string + - name: Upload release assets for DotSlash + uses: actions/github-script@v6 + env: + RELEASE_ID: ${{ steps.create-draft-release.outputs.result }} + with: + script: | + const {uploadReleaseAssetsForDotSlashFiles} = require('./scripts/releases/upload-release-assets-for-dotslash.js'); + const version = '${{ github.ref_name }}'; + await uploadReleaseAssetsForDotSlashFiles({ + version, + token: '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}', + releaseId: process.env.RELEASE_ID, + }); diff --git a/.github/workflows/validate-dotslash-artifacts.yml b/.github/workflows/validate-dotslash-artifacts.yml new file mode 100644 index 000000000000..2b62d3b34c8e --- /dev/null +++ b/.github/workflows/validate-dotslash-artifacts.yml @@ -0,0 +1,48 @@ +name: Validate DotSlash Artifacts + +on: + workflow_dispatch: + release: + types: [published] + push: + branches: + - main + paths: + - packages/debugger-shell/bin/react-native-devtools + - "scripts/releases/**" + - package.json + - yarn.lock + pull_request: + branches: + - main + paths: + - packages/debugger-shell/bin/react-native-devtools + - "scripts/releases/**" + - package.json + - yarn.lock + # Same time as the nightly build: 2:15 AM UTC + schedule: + - cron: "15 2 * * *" + +jobs: + validate-dotslash-artifacts: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + - name: Install dependencies + uses: ./.github/actions/yarn-install + - name: Configure Git + shell: bash + run: | + git config --local user.email "bot@reactnative.dev" + git config --local user.name "React Native Bot" + - name: Validate DotSlash artifacts + uses: actions/github-script@v6 + with: + script: | + const {validateDotSlashArtifacts} = require('./scripts/releases/validate-dotslash-artifacts.js'); + await validateDotSlashArtifacts(); diff --git a/flow-typed/npm/@expo/spawn-async_v1.x.x.js b/flow-typed/npm/@expo/spawn-async_v1.x.x.js new file mode 100644 index 000000000000..45c6187c9aac --- /dev/null +++ b/flow-typed/npm/@expo/spawn-async_v1.x.x.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +declare module '@expo/spawn-async' { + type SpawnOptions = { + cwd?: string, + env?: Object, + argv0?: string, + stdio?: string | Array, + detached?: boolean, + uid?: number, + gid?: number, + shell?: boolean | string, + windowsVerbatimArguments?: boolean, + windowsHide?: boolean, + encoding?: string, + ignoreStdio?: boolean, + }; + + declare class SpawnPromise extends Promise { + child: child_process$ChildProcess; + } + type SpawnResult = { + pid?: number, + output: string[], + stdout: string, + stderr: string, + status: number | null, + signal: string | null, + }; + + declare function spawnAsync( + command: string, + args?: $ReadOnlyArray, + options?: SpawnOptions, + ): SpawnPromise; + + declare module.exports: typeof spawnAsync; +} diff --git a/flow-typed/npm/@octokit/rest_v22.x.x.js b/flow-typed/npm/@octokit/rest_v22.x.x.js new file mode 100644 index 000000000000..9c193173e2cb --- /dev/null +++ b/flow-typed/npm/@octokit/rest_v22.x.x.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +// Partial types for Octokit based on the usage in react-native-github +declare module '@octokit/rest' { + declare class Octokit { + constructor(options?: {auth?: string, ...}): this; + + repos: $ReadOnly<{ + listReleaseAssets: ( + params: $ReadOnly<{ + owner: string, + repo: string, + release_id: string, + }>, + ) => Promise<{ + data: Array<{ + id: string, + name: string, + ... + }>, + ... + }>, + uploadReleaseAsset: ( + params: $ReadOnly<{ + owner: string, + repo: string, + release_id: string, + name: string, + data: Buffer, + headers: $ReadOnly<{ + 'content-type': string, + ... + }>, + ... + }>, + ) => Promise<{ + data: { + browser_download_url: string, + ... + }, + ... + }>, + deleteReleaseAsset: (params: { + owner: string, + repo: string, + asset_id: string, + ... + }) => Promise, + }>; + } + + declare export {Octokit}; +} diff --git a/flow-typed/npm/fb-dotslash_v0.x.x.js b/flow-typed/npm/fb-dotslash_v0.x.x.js new file mode 100644 index 000000000000..41c01f297da7 --- /dev/null +++ b/flow-typed/npm/fb-dotslash_v0.x.x.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +declare module 'fb-dotslash' { + declare module.exports: string; +} diff --git a/flow-typed/npm/jsonc-parser_v2.2.x.js b/flow-typed/npm/jsonc-parser_v2.2.x.js new file mode 100644 index 000000000000..50316cb13e91 --- /dev/null +++ b/flow-typed/npm/jsonc-parser_v2.2.x.js @@ -0,0 +1,421 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +declare module 'jsonc-parser' { + /** + * Creates a JSON scanner on the given text. + * If ignoreTrivia is set, whitespaces or comments are ignored. + */ + declare export const createScanner: ( + text: string, + ignoreTrivia?: boolean, + ) => JSONScanner; + export type ScanError = number; + export type SyntaxKind = number; + /** + * The scanner object, representing a JSON scanner at a position in the input string. + */ + export type JSONScanner = $ReadOnly<{ + /** + * Sets the scan position to a new offset. A call to 'scan' is needed to get the first token. + */ + setPosition(pos: number): void, + /** + * Read the next token. Returns the token code. + */ + scan(): SyntaxKind, + /** + * Returns the zero-based current scan position, which is after the last read token. + */ + getPosition(): number, + /** + * Returns the last read token. + */ + getToken(): SyntaxKind, + /** + * Returns the last read token value. The value for strings is the decoded string content. For numbers it's of type number, for boolean it's true or false. + */ + getTokenValue(): string, + /** + * The zero-based start offset of the last read token. + */ + getTokenOffset(): number, + /** + * The length of the last read token. + */ + getTokenLength(): number, + /** + * The zero-based start line number of the last read token. + */ + getTokenStartLine(): number, + /** + * The zero-based start character (column) of the last read token. + */ + getTokenStartCharacter(): number, + /** + * An error code of the last scan. + */ + getTokenError(): ScanError, + }>; + /** + * For a given offset, evaluate the location in the JSON document. Each segment in the location path is either a property name or an array index. + */ + declare export const getLocation: ( + text: string, + position: number, + ) => Location; + /** + * Parses the given text and returns the object the JSON content represents. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result. + * Therefore, always check the errors list to find out if the input was valid. + */ + declare export const parse: ( + text: string, + errors?: ParseError[], + options?: ParseOptions, + ) => any; + /** + * Parses the given text and returns a tree representation the JSON content. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result. + */ + declare export const parseTree: ( + text: string, + errors?: ParseError[], + options?: ParseOptions, + ) => Node | void; + /** + * Finds the node at the given path in a JSON DOM. + */ + declare export const findNodeAtLocation: ( + root: Node, + path: JSONPath, + ) => Node | void; + /** + * Finds the innermost node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset. + */ + declare export const findNodeAtOffset: ( + root: Node, + offset: number, + includeRightBound?: boolean, + ) => Node | void; + /** + * Gets the JSON path of the given JSON DOM node + */ + declare export const getNodePath: (node: Node) => JSONPath; + /** + * Evaluates the JavaScript object of the given JSON DOM node + */ + declare export const getNodeValue: (node: Node) => any; + /** + * Parses the given text and invokes the visitor functions for each object, array and literal reached. + */ + declare export const visit: ( + text: string, + visitor: JSONVisitor, + options?: ParseOptions, + ) => any; + /** + * Takes JSON with JavaScript-style comments and remove + * them. Optionally replaces every none-newline character + * of comments with a replaceCharacter + */ + declare export const stripComments: ( + text: string, + replaceCh?: string, + ) => string; + export type ParseError = { + error: ParseErrorCode, + offset: number, + length: number, + }; + export type ParseErrorCode = number; + declare export function printParseErrorCode( + code: ParseErrorCode, + ): + | 'InvalidSymbol' + | 'InvalidNumberFormat' + | 'PropertyNameExpected' + | 'ValueExpected' + | 'ColonExpected' + | 'CommaExpected' + | 'CloseBraceExpected' + | 'CloseBracketExpected' + | 'EndOfFileExpected' + | 'InvalidCommentToken' + | 'UnexpectedEndOfComment' + | 'UnexpectedEndOfString' + | 'UnexpectedEndOfNumber' + | 'InvalidUnicode' + | 'InvalidEscapeCharacter' + | 'InvalidCharacter' + | ''; + export type NodeType = + | 'object' + | 'array' + | 'property' + | 'string' + | 'number' + | 'boolean' + | 'null'; + export type Node = { + type: NodeType, + value?: any, + offset: number, + length: number, + colonOffset?: number, + parent?: Node, + children?: Node[], + }; + /** + * A {@linkcode JSONPath} segment. Either a string representing an object property name + * or a number (starting at 0) for array indices. + */ + export type Segment = string | number; + export type JSONPath = Segment[]; + export type Location = { + /** + * The previous property key or literal value (string, number, boolean or null) or undefined. + */ + previousNode?: Node, + /** + * The path describing the location in the JSON document. The path consists of a sequence of strings + * representing an object property or numbers for array indices. + */ + path: JSONPath, + /** + * Matches the locations path against a pattern consisting of strings (for properties) and numbers (for array indices). + * '*' will match a single segment of any property name or index. + * '**' will match a sequence of segments of any property name or index, or no segment. + */ + matches: (patterns: JSONPath) => boolean, + /** + * If set, the location's offset is at a property key. + */ + isAtPropertyKey: boolean, + }; + export type ParseOptions = { + disallowComments?: boolean, + allowTrailingComma?: boolean, + allowEmptyContent?: boolean, + }; + /** + * Visitor called by {@linkcode visit} when parsing JSON. + * + * The visitor functions have the following common parameters: + * - `offset`: Global offset within the JSON document, starting at 0 + * - `startLine`: Line number, starting at 0 + * - `startCharacter`: Start character (column) within the current line, starting at 0 + * + * Additionally some functions have a `pathSupplier` parameter which can be used to obtain the + * current `JSONPath` within the document. + */ + export type JSONVisitor = { + /** + * Invoked when an open brace is encountered and an object is started. The offset and length represent the location of the open brace. + */ + onObjectBegin?: ( + offset: number, + length: number, + startLine: number, + startCharacter: number, + pathSupplier: () => JSONPath, + ) => void, + /** + * Invoked when a property is encountered. The offset and length represent the location of the property name. + * The `JSONPath` created by the `pathSupplier` refers to the enclosing JSON object, it does not include the + * property name yet. + */ + onObjectProperty?: ( + property: string, + offset: number, + length: number, + startLine: number, + startCharacter: number, + pathSupplier: () => JSONPath, + ) => void, + /** + * Invoked when a closing brace is encountered and an object is completed. The offset and length represent the location of the closing brace. + */ + onObjectEnd?: ( + offset: number, + length: number, + startLine: number, + startCharacter: number, + ) => void, + /** + * Invoked when an open bracket is encountered. The offset and length represent the location of the open bracket. + */ + onArrayBegin?: ( + offset: number, + length: number, + startLine: number, + startCharacter: number, + pathSupplier: () => JSONPath, + ) => void, + /** + * Invoked when a closing bracket is encountered. The offset and length represent the location of the closing bracket. + */ + onArrayEnd?: ( + offset: number, + length: number, + startLine: number, + startCharacter: number, + ) => void, + /** + * Invoked when a literal value is encountered. The offset and length represent the location of the literal value. + */ + onLiteralValue?: ( + value: any, + offset: number, + length: number, + startLine: number, + startCharacter: number, + pathSupplier: () => JSONPath, + ) => void, + /** + * Invoked when a comma or colon separator is encountered. The offset and length represent the location of the separator. + */ + onSeparator?: ( + character: string, + offset: number, + length: number, + startLine: number, + startCharacter: number, + ) => void, + /** + * When comments are allowed, invoked when a line or block comment is encountered. The offset and length represent the location of the comment. + */ + onComment?: ( + offset: number, + length: number, + startLine: number, + startCharacter: number, + ) => void, + /** + * Invoked on an error. + */ + onError?: ( + error: ParseErrorCode, + offset: number, + length: number, + startLine: number, + startCharacter: number, + ) => void, + }; + /** + * An edit result describes a textual edit operation. It is the result of a {@linkcode format} and {@linkcode modify} operation. + * It consist of one or more edits describing insertions, replacements or removals of text segments. + * * The offsets of the edits refer to the original state of the document. + * * No two edits change or remove the same range of text in the original document. + * * Multiple edits can have the same offset if they are multiple inserts, or an insert followed by a remove or replace. + * * The order in the array defines which edit is applied first. + * To apply an edit result use {@linkcode applyEdits}. + * In general multiple EditResults must not be concatenated because they might impact each other, producing incorrect or malformed JSON data. + */ + export type EditResult = Edit[]; + /** + * Represents a text modification + */ + export type Edit = { + /** + * The start offset of the modification. + */ + offset: number, + /** + * The length of the modification. Must not be negative. Empty length represents an *insert*. + */ + length: number, + /** + * The new content. Empty content represents a *remove*. + */ + content: string, + }; + /** + * A text range in the document + */ + export type Range = { + /** + * The start offset of the range. + */ + offset: number, + /** + * The length of the range. Must not be negative. + */ + length: number, + }; + /** + * Options used by {@linkcode format} when computing the formatting edit operations + */ + export type FormattingOptions = $ReadOnly<{ + /** + * If indentation is based on spaces (`insertSpaces` = true), the number of spaces that make an indent. + */ + tabSize?: number, + /** + * Is indentation based on spaces? + */ + insertSpaces?: boolean, + /** + * The default 'end of line' character. If not set, '\n' is used as default. + */ + eol?: string, + }>; + /** + * Computes the edit operations needed to format a JSON document. + * + * @param documentText The input text + * @param range The range to format or `undefined` to format the full content + * @param options The formatting options + * @returns The edit operations describing the formatting changes to the original document following the format described in {@linkcode EditResult}. + * To apply the edit operations to the input, use {@linkcode applyEdits}. + */ + declare export function format( + documentText: string, + range: Range | void, + options: FormattingOptions, + ): EditResult; + /** + * Options used by {@linkcode modify} when computing the modification edit operations + */ + export type ModificationOptions = { + /** + * Formatting options. + */ + formattingOptions: FormattingOptions, + /** + * Optional function to define the insertion index given an existing list of properties. + */ + getInsertionIndex?: (properties: string[]) => number, + }; + /** + * Computes the edit operations needed to modify a value in the JSON document. + * + * @param documentText The input text + * @param path The path of the value to change. The path represents either to the document root, a property or an array item. + * If the path points to an non-existing property or item, it will be created. + * @param value The new value for the specified property or item. If the value is undefined, + * the property or item will be removed. + * @param options Options + * @returns The edit operations describing the changes to the original document, following the format described in {@linkcode EditResult}. + * To apply the edit operations to the input, use {@linkcode applyEdits}. + */ + declare export function modify( + text: string, + path: JSONPath, + value: any, + options: ModificationOptions, + ): EditResult; + /** + * Applies edits to an input string. + * @param text The input text + * @param edits Edit operations following the format described in {@linkcode EditResult}. + * @returns The text with the applied edits. + * @throws An error if the edit operations are not well-formed as described in {@linkcode EditResult}. + */ + declare export function applyEdits(text: string, edits: EditResult): string; +} diff --git a/package.json b/package.json index ecaaf19ed11d..df0ce9eb20c6 100644 --- a/package.json +++ b/package.json @@ -54,13 +54,16 @@ "@babel/preset-env": "^7.25.3", "@babel/preset-flow": "^7.24.7", "@electron/packager": "^18.3.6", + "@expo/spawn-async": "^1.7.2", "@jest/create-cache-key-function": "^29.7.0", "@microsoft/api-extractor": "^7.52.2", + "@octokit/rest": "^22.0.0", "@react-native/metro-babel-transformer": "0.82.0-main", "@react-native/metro-config": "0.82.0-main", "@tsconfig/node22": "22.0.2", "@types/react": "^19.1.0", "@typescript-eslint/parser": "^8.36.0", + "ansi-regex": "^5.0.0", "ansi-styles": "^4.2.1", "babel-plugin-minify-dead-code-elimination": "^0.5.2", "babel-plugin-syntax-hermes-parser": "0.32.0", @@ -81,6 +84,7 @@ "eslint-plugin-react-native": "^4.0.0", "eslint-plugin-redundant-undefined": "^0.4.0", "eslint-plugin-relay": "^1.8.3", + "fb-dotslash": "0.5.8", "flow-api-translator": "0.32.0", "flow-bin": "^0.280.0", "glob": "^7.1.1", @@ -93,6 +97,7 @@ "jest-diff": "^29.7.0", "jest-junit": "^16.0.0", "jest-snapshot": "^29.7.0", + "jsonc-parser": "2.2.1", "markdownlint-cli2": "^0.17.2", "markdownlint-rule-relative-links": "^3.0.0", "memfs": "^4.7.7", diff --git a/packages/debugger-shell/src/node/index.flow.js b/packages/debugger-shell/src/node/index.flow.js index 06ba4a9eab67..f76fbc2af241 100644 --- a/packages/debugger-shell/src/node/index.flow.js +++ b/packages/debugger-shell/src/node/index.flow.js @@ -152,11 +152,7 @@ function getShellBinaryAndArgs( ): [string, Array] { switch (flavor) { case 'prebuilt': - return [ - // $FlowFixMe[cannot-resolve-module] fb-dotslash includes Flow types but Flow does not pick them up - require('fb-dotslash'), - [DEVTOOLS_BINARY_DOTSLASH_FILE], - ]; + return [require('fb-dotslash'), [DEVTOOLS_BINARY_DOTSLASH_FILE]]; case 'dev': return [ // NOTE: Internally at Meta, this is aliased to a workspace that is diff --git a/packages/debugger-shell/src/node/private/LaunchUtils.js b/packages/debugger-shell/src/node/private/LaunchUtils.js index f73b36201498..a0692f64ee09 100644 --- a/packages/debugger-shell/src/node/private/LaunchUtils.js +++ b/packages/debugger-shell/src/node/private/LaunchUtils.js @@ -44,11 +44,11 @@ async function spawnAndGetStderr( async function prepareDebuggerShellFromDotSlashFile( filePath: string, ): Promise { - const {code, stderr} = await spawnAndGetStderr( - // $FlowFixMe[cannot-resolve-module] fb-dotslash includes Flow types but Flow does not pick them up - require('fb-dotslash'), - ['--', 'fetch', filePath], - ); + const {code, stderr} = await spawnAndGetStderr(require('fb-dotslash'), [ + '--', + 'fetch', + filePath, + ]); if (code === 0) { return {code: 'success'}; } diff --git a/scripts/releases/__tests__/__snapshots__/upload-release-assets-for-dotslash-test.js.snap b/scripts/releases/__tests__/__snapshots__/upload-release-assets-for-dotslash-test.js.snap new file mode 100644 index 000000000000..210fcdc260df --- /dev/null +++ b/scripts/releases/__tests__/__snapshots__/upload-release-assets-for-dotslash-test.js.snap @@ -0,0 +1,203 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`uploadReleaseAssetsForDotSlashFile deletes and reuploads the asset if force is true: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Deleting existing release asset...", + ], + Array [ + "[test.tar.gz] Downloading from ...", + ], + Array [ + "[test.tar.gz] Validating download...", + ], + Array [ + "[test.tar.gz] Uploading to release...", + ], + Array [ + "[test.tar.gz] Uploaded to https://github.com/facebook/react-native/releases/download/untagged-0b602d8af97c6d3b784c/test.tar.gz", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile deletes and reuploads the asset if force is true: deleteReleaseAsset calls 1`] = ` +Array [ + Array [ + Object { + "asset_id": "1", + "owner": "facebook", + "repo": "react-native", + }, + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile deletes and reuploads the asset if force is true: uploadReleaseAsset calls 1`] = ` +Array [ + Array [ + Object { + "data": Object { + "data": Array [], + "type": "Buffer", + }, + "headers": Object { + "content-type": "text/plain", + }, + "name": "test.tar.gz", + "owner": "facebook", + "release_id": "1", + "repo": "react-native", + }, + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile does not overwrite an existing asset if dryRun is true: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Skipping existing release asset...", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile does not upload the asset if dryRun is true: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Downloading from ...", + ], + Array [ + "[test.tar.gz] Validating download...", + ], + Array [ + "[test.tar.gz] Dry run: Not uploading to release.", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if asset has been renamed by GitHub 1`] = `"Asset name was changed while uploading to the draft release: expected test.tar.gz, got test-renamed.tar.gz. /entry-point has already been published to npm with the following URL, which will not work when the release is published on GitHub: https://github.com/facebook/react-native/releases/download/v1000.0.1/test.tar.gz"`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if asset has been renamed by GitHub: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Downloading from ...", + ], + Array [ + "[test.tar.gz] Validating download...", + ], + Array [ + "[test.tar.gz] Uploading to release...", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if asset has been renamed by GitHub: uploadReleaseAsset calls 1`] = ` +Array [ + Array [ + Object { + "data": Object { + "data": Array [], + "type": "Buffer", + }, + "headers": Object { + "content-type": "text/plain", + }, + "name": "test.tar.gz", + "owner": "facebook", + "release_id": "1", + "repo": "react-native", + }, + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if the upstream asset is corrupt 1`] = `"size mismatch: expected 1, got 0"`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if the upstream asset is corrupt: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Downloading from ...", + ], + Array [ + "[test.tar.gz] Validating download...", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if the upstream asset is unreachable 1`] = `"curl --silent --location --output /data /error --write-out %{content_type} --fail exited with non-zero code: 22"`; + +exports[`uploadReleaseAssetsForDotSlashFile fails loudly if the upstream asset is unreachable: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Downloading from /error...", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile skips uploading the asset if already present: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Skipping existing release asset...", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile uploads the asset if not already present: console.log calls 1`] = ` +Array [ + Array [ + "Uploading assets for /entry-point...", + ], + Array [ + "[test.tar.gz] Downloading from ...", + ], + Array [ + "[test.tar.gz] Validating download...", + ], + Array [ + "[test.tar.gz] Uploading to release...", + ], + Array [ + "[test.tar.gz] Uploaded to https://github.com/facebook/react-native/releases/download/untagged-0b602d8af97c6d3b784c/test.tar.gz", + ], +] +`; + +exports[`uploadReleaseAssetsForDotSlashFile uploads the asset if not already present: uploadReleaseAsset calls 1`] = ` +Array [ + Array [ + Object { + "data": Object { + "data": Array [], + "type": "Buffer", + }, + "headers": Object { + "content-type": "text/plain", + }, + "name": "test.tar.gz", + "owner": "facebook", + "release_id": "1", + "repo": "react-native", + }, + ], +] +`; diff --git a/scripts/releases/__tests__/__snapshots__/write-dotslash-release-asset-urls-test.js.snap b/scripts/releases/__tests__/__snapshots__/write-dotslash-release-asset-urls-test.js.snap new file mode 100644 index 000000000000..8639e3c11a73 --- /dev/null +++ b/scripts/releases/__tests__/__snapshots__/write-dotslash-release-asset-urls-test.js.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`writeReleaseAssetUrlsToDotSlashFile adds a new release asset provider if missing (first release commit in a branch): console.log calls 1`] = ` +Array [ + Array [ + "Updating /entry-point...", + ], + Array [ + "Downloading from for integrity validation...", + ], + Array [ + "Providers: +", + "- Original ++ Updated + + Array [ + Object { ++ \\"url\\": \\"https://github.com/facebook/react-native/releases/download/v1000.0.1/test-linux-x86_64\\", ++ }, ++ Object { + \\"url\\": \\"\\", + }, + ]", + ], +] +`; + +exports[`writeReleaseAssetUrlsToDotSlashFile adds a new release asset provider if missing (first release commit in a branch): updated dotslash file 1`] = ` +"#!/usr/bin/env dotslash +// @generated SignedSource<> +{ + \\"name\\": \\"test\\", + \\"platforms\\": { + \\"linux-x86_64\\": { + \\"providers\\": [ + { + \\"url\\": \\"https://github.com/facebook/react-native/releases/download/v1000.0.1/test-linux-x86_64\\" + }, + { + \\"url\\": \\"\\" + } + ], + \\"size\\": 0, + \\"hash\\": \\"sha256\\", + \\"digest\\": \\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\\", + \\"path\\": \\"bar\\" + } + } +} +" +`; + +exports[`writeReleaseAssetUrlsToDotSlashFile fails if there are no upstream providers 1`] = `"No upstream HTTP providers found for asset: test-linux-x86_64.tar.gz"`; + +exports[`writeReleaseAssetUrlsToDotSlashFile fails if there are no upstream providers: console.log calls 1`] = ` +Array [ + Array [ + "Updating /entry-point...", + ], +] +`; + +exports[`writeReleaseAssetUrlsToDotSlashFile fails if upstream returns an incorrect asset 1`] = `"size mismatch: expected 1, got 0"`; + +exports[`writeReleaseAssetUrlsToDotSlashFile fails if upstream returns an incorrect asset: console.log calls 1`] = ` +Array [ + Array [ + "Updating /entry-point...", + ], + Array [ + "Downloading from for integrity validation...", + ], +] +`; + +exports[`writeReleaseAssetUrlsToDotSlashFile replaces the old release asset provider if exists (Nth release commit in a branch): console.log calls 1`] = ` +Array [ + Array [ + "Updating /entry-point...", + ], + Array [ + "Downloading from for integrity validation...", + ], + Array [ + "Providers: +", + "- Original ++ Updated + + Array [ + Object { +- \\"url\\": \\"\\", ++ \\"url\\": \\"https://github.com/facebook/react-native/releases/download/v1000.0.1/test-linux-x86_64\\", + }, + Object { +- \\"url\\": \\"https://github.com/facebook/react-native/releases/download/v1000.0.0/test.tar.gz\\", ++ \\"url\\": \\"\\", + }, + ]", + ], +] +`; + +exports[`writeReleaseAssetUrlsToDotSlashFile replaces the old release asset provider if exists (Nth release commit in a branch): updated dotslash file 1`] = ` +"#!/usr/bin/env dotslash +// @generated SignedSource<> +{ + \\"name\\": \\"test\\", + \\"platforms\\": { + \\"linux-x86_64\\": { + \\"providers\\": [ + { + \\"url\\": \\"https://github.com/facebook/react-native/releases/download/v1000.0.1/test-linux-x86_64\\" + }, + { + \\"url\\": \\"\\" + } + ], + \\"size\\": 0, + \\"hash\\": \\"sha256\\", + \\"digest\\": \\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\\", + \\"path\\": \\"bar\\" + } + } +} +" +`; diff --git a/scripts/releases/__tests__/snapshot-utils.js b/scripts/releases/__tests__/snapshot-utils.js new file mode 100644 index 000000000000..5a0103a88c2e --- /dev/null +++ b/scripts/releases/__tests__/snapshot-utils.js @@ -0,0 +1,92 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import ansiRegex from 'ansi-regex'; + +const { + getTempDirPatternForTests: getCurlTempDirPattern, +} = require('../utils/curl-utils'); +const invariant = require('invariant'); + +/** + * Returns a Jest snapshot serializer that replaces the given token or pattern + * with the given replacement. + */ +function sanitizeSnapshots( + tokenOrPattern: string | RegExp | (() => string | RegExp), + replacement: string, +): JestPrettyFormatPlugin { + const test = (val: mixed) => { + if (typeof val !== 'string') { + return false; + } + let tokenOrPatternToTest = tokenOrPattern; + if (typeof tokenOrPatternToTest === 'function') { + tokenOrPatternToTest = tokenOrPatternToTest(); + } + if (typeof tokenOrPatternToTest === 'string') { + return val.includes(tokenOrPatternToTest); + } + return tokenOrPatternToTest.test(val); + }; + const serialize = ( + val: mixed, + config: mixed, + indentation: mixed, + depth: mixed, + refs: mixed, + // $FlowFixMe[unclear-type] TODO: add up-to-date and accurate types for Jest snapshot serializers. + printer: any, + ) => { + invariant(typeof val === 'string', 'Received non-string value.'); + let tokenOrPatternToTest = tokenOrPattern; + if (typeof tokenOrPatternToTest === 'function') { + tokenOrPatternToTest = tokenOrPatternToTest(); + } + const replacedVal = val.replaceAll(tokenOrPatternToTest, replacement); + if (test(replacedVal)) { + // Recursion breaker. + throw new Error( + `Failed to sanitize snapshot: ${replacedVal} still contains ${tokenOrPatternToTest.toString()}`, + ); + } + return printer(replacedVal, config, indentation, depth, refs, printer); + }; + return { + serialize, + test, + // $FlowFixMe[unclear-type] expect.addSnapshotSerializer is typed inaccurately + } as any as JestPrettyFormatPlugin; +} + +/** + * A Jest snapshot serializer that removes ANSI color codes from strings. + */ +const removeAnsiColors = sanitizeSnapshots( + ansiRegex(), + '', +) as JestPrettyFormatPlugin; + +/** + * A Jest snapshot serializer that redacts the exact temporary directory path + * used by curl-utils. + */ +const removeCurlPaths = sanitizeSnapshots( + getCurlTempDirPattern(), + '', +) as JestPrettyFormatPlugin; + +module.exports = { + sanitizeSnapshots, + removeAnsiColors, + removeCurlPaths, +}; diff --git a/scripts/releases/__tests__/upload-release-assets-for-dotslash-test.js b/scripts/releases/__tests__/upload-release-assets-for-dotslash-test.js new file mode 100644 index 000000000000..4eae57114c44 --- /dev/null +++ b/scripts/releases/__tests__/upload-release-assets-for-dotslash-test.js @@ -0,0 +1,351 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const { + getReleaseAssetMap, + uploadReleaseAssetsForDotSlashFile, +} = require('../upload-release-assets-for-dotslash'); +const { + removeAnsiColors, + removeCurlPaths, + sanitizeSnapshots, +} = require('./snapshot-utils'); +const fs = require('fs/promises'); +const http = require('http'); +const os = require('os'); +const path = require('path'); + +let server, serverUrl, tmpDir, consoleLog; + +expect.addSnapshotSerializer(sanitizeSnapshots(() => tmpDir, '')); +expect.addSnapshotSerializer(sanitizeSnapshots(() => serverUrl, '')); +expect.addSnapshotSerializer(removeAnsiColors); +expect.addSnapshotSerializer(removeCurlPaths); + +const mockAssets: Array<{ + id: string, + ... +}> = []; + +let nextAssetId = 1; + +const octokit = { + repos: { + listReleaseAssets: jest.fn().mockImplementation(() => { + return { + data: mockAssets, + }; + }), + deleteReleaseAsset: jest.fn().mockImplementation(({asset_id}) => { + const index = mockAssets.findIndex(asset => asset.id === asset_id); + if (index === -1) { + throw new Error('Asset not found'); + } + mockAssets.splice(index, 1); + }), + uploadReleaseAsset: jest.fn().mockImplementation(() => { + let assetId; + do { + assetId = String(nextAssetId++); + } while (mockAssets.some(asset => asset.id === assetId)); + mockAssets.push({ + id: assetId, + }); + return { + data: { + id: assetId, + browser_download_url: `https://github.com/facebook/react-native/releases/download/untagged-0b602d8af97c6d3b784c/test.tar.gz`, + }, + }; + }), + }, +}; + +beforeEach(async () => { + mockAssets.length = 0; + octokit.repos.listReleaseAssets.mockClear(); + octokit.repos.deleteReleaseAsset.mockClear(); + octokit.repos.uploadReleaseAsset.mockClear(); + + consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + tmpDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'upload-release-assets-for-dotslash-test-'), + ); + await new Promise((resolve, reject) => { + server = http.createServer((req, res) => { + if (req.url !== '/') { + res.writeHead(404); + res.end(); + return; + } + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end(''); + }); + server.on('error', reject); + server.listen(0, 'localhost', () => { + const {port} = server.address(); + serverUrl = `http://localhost:${port}`; + resolve(); + }); + }); +}); + +afterEach(async () => { + consoleLog.mockRestore(); + await new Promise((resolve, reject) => { + server.close(err => { + if (err) { + reject(err); + } + resolve(); + }); + }); + await fs.rm(tmpDir, {recursive: true, force: true}); +}); + +describe('uploadReleaseAssetsForDotSlashFile', () => { + beforeEach(async () => { + // Simulate the repo in a state where the DotSlash file has been updated + // (by write-release-asset-urls-to-dotslash-file) but the release assets + // have not been uploaded yet. + const dotslashContents = `#!/usr/bin/env dotslash +{ + "name": "test", + "platforms": { + "linux-x86_64": { + "providers": [ + {"url": "https://github.com/facebook/react-native/releases/download/v1000.0.1/test.tar.gz"}, + {"url": "${serverUrl}"} + ], + "size": 0, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": "tar.gz", + "path": "bar" + }, + }, +}`; + await fs.writeFile(path.join(tmpDir, 'entry-point'), dotslashContents); + }); + + const releaseId = '1'; + + test('uploads the asset if not already present', async () => { + await uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: false, + }, + octokit, + ); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset.mock.calls).toMatchSnapshot( + 'uploadReleaseAsset calls', + ); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('skips uploading the asset if already present', async () => { + mockAssets.push({ + id: '1', + name: 'test.tar.gz', + }); + await uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: false, + }, + octokit, + ); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset).not.toHaveBeenCalled(); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('deletes and reuploads the asset if force is true', async () => { + mockAssets.push({ + id: '1', + name: 'test.tar.gz', + }); + await uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: true, + dryRun: false, + }, + octokit, + ); + + expect(octokit.repos.deleteReleaseAsset.mock.calls).toMatchSnapshot( + 'deleteReleaseAsset calls', + ); + expect(octokit.repos.uploadReleaseAsset.mock.calls).toMatchSnapshot( + 'uploadReleaseAsset calls', + ); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('does not upload the asset if dryRun is true', async () => { + await uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: true, + }, + octokit, + ); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset).not.toHaveBeenCalled(); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('does not overwrite an existing asset if dryRun is true', async () => { + mockAssets.push({ + id: '1', + name: 'test.tar.gz', + }); + await uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: true, + }, + octokit, + ); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset).not.toHaveBeenCalled(); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('fails loudly if asset has been renamed by GitHub', async () => { + octokit.repos.uploadReleaseAsset.mockImplementationOnce(async () => { + return { + data: { + id: '1', + browser_download_url: `https://github.com/facebook/react-native/releases/download/untagged-0b602d8af97c6d3b784c/test-renamed.tar.gz`, + }, + }; + }); + await expect( + uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: false, + }, + octokit, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset.mock.calls).toMatchSnapshot( + 'uploadReleaseAsset calls', + ); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('fails loudly if the upstream asset is unreachable', async () => { + const dotslashContents = await fs.readFile( + path.join(tmpDir, 'entry-point'), + 'utf8', + ); + await fs.writeFile( + path.join(tmpDir, 'entry-point'), + dotslashContents.replace(serverUrl, `${serverUrl}/error`), + ); + await expect( + uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: false, + }, + octokit, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset).not.toHaveBeenCalled(); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('fails loudly if the upstream asset is corrupt', async () => { + const dotslashContents = await fs.readFile( + path.join(tmpDir, 'entry-point'), + 'utf8', + ); + await fs.writeFile( + path.join(tmpDir, 'entry-point'), + dotslashContents.replace('"size": 0', `"size": 1`), + ); + await expect( + uploadReleaseAssetsForDotSlashFile( + path.join(tmpDir, 'entry-point'), + { + releaseId, + releaseTag: 'v1000.0.1', + existingAssetsByName: await getReleaseAssetMap({releaseId}, octokit), + }, + { + force: false, + dryRun: false, + }, + octokit, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + + expect(octokit.repos.deleteReleaseAsset).not.toHaveBeenCalled(); + expect(octokit.repos.uploadReleaseAsset).not.toHaveBeenCalled(); + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); +}); diff --git a/scripts/releases/__tests__/write-dotslash-release-asset-urls-test.js b/scripts/releases/__tests__/write-dotslash-release-asset-urls-test.js new file mode 100644 index 000000000000..ea8acf48720c --- /dev/null +++ b/scripts/releases/__tests__/write-dotslash-release-asset-urls-test.js @@ -0,0 +1,203 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const { + writeReleaseAssetUrlsToDotSlashFile, +} = require('../write-dotslash-release-asset-urls'); +const {removeAnsiColors, sanitizeSnapshots} = require('./snapshot-utils'); +const fs = require('fs/promises'); +const http = require('http'); +const os = require('os'); +const path = require('path'); +const signedsource = require('signedsource'); + +let server, serverUrl, tmpDir, consoleLog; + +expect.addSnapshotSerializer(sanitizeSnapshots(() => tmpDir, '')); +expect.addSnapshotSerializer(sanitizeSnapshots(() => serverUrl, '')); +expect.addSnapshotSerializer( + sanitizeSnapshots( + /SignedSource<<[a-f0-9]{32}>>/g, + 'SignedSource<>', + ), +); +expect.addSnapshotSerializer(removeAnsiColors); + +beforeEach(async () => { + consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + tmpDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'write-dotslash-release-asset-urls-test-'), + ); + await new Promise((resolve, reject) => { + server = http.createServer((req, res) => { + if (req.url !== '/') { + res.writeHead(404); + res.end(); + return; + } + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end(''); + }); + server.on('error', reject); + server.listen(0, 'localhost', () => { + const {port} = server.address(); + serverUrl = `http://localhost:${port}`; + resolve(); + }); + }); +}); + +afterEach(async () => { + consoleLog.mockRestore(); + await new Promise((resolve, reject) => { + server.close(err => { + if (err) { + reject(err); + } + resolve(); + }); + }); + await fs.rm(tmpDir, {recursive: true, force: true}); +}); + +describe('writeReleaseAssetUrlsToDotSlashFile', () => { + test('fails if there are no upstream providers', async () => { + const dotslashContents = `#!/usr/bin/env dotslash +{ + "name": "test", + "platforms": { + "linux-x86_64": { + "providers": [ + {"url": "https://github.com/facebook/react-native/releases/download/v1000.0.0/test.tar.gz"}, + ], + "size": 0, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": "tar.gz", + "path": "bar" + } + } +} +`; + await fs.writeFile(`${tmpDir}/entry-point`, dotslashContents); + + await expect( + writeReleaseAssetUrlsToDotSlashFile({ + filename: `${tmpDir}/entry-point`, + releaseTag: 'v1000.0.1', + }), + ).rejects.toThrowErrorMatchingSnapshot(); + + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); + + test('adds a new release asset provider if missing (first release commit in a branch)', async () => { + const dotslashContents = `#!/usr/bin/env dotslash +// @${'generated SignedSource<<00000000000000000000000000000000>>'} +{ + "name": "test", + "platforms": { + "linux-x86_64": { + "providers": [ + {"url": "${serverUrl}"}, + ], + "size": 0, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "path": "bar" + } + } +} +`; + await fs.writeFile(`${tmpDir}/entry-point`, dotslashContents); + + await expect( + writeReleaseAssetUrlsToDotSlashFile({ + filename: `${tmpDir}/entry-point`, + releaseTag: 'v1000.0.1', + }), + ).resolves.toBeUndefined(); + + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + + const updatedContents = await fs.readFile(`${tmpDir}/entry-point`, 'utf8'); + + expect(updatedContents).toMatchSnapshot('updated dotslash file'); + expect(signedsource.verifySignature(updatedContents)).toBe(true); + }); + + test('replaces the old release asset provider if exists (Nth release commit in a branch)', async () => { + const dotslashContents = `#!/usr/bin/env dotslash +// @${'generated SignedSource<<00000000000000000000000000000000>>'} +{ + "name": "test", + "platforms": { + "linux-x86_64": { + "providers": [ + {"url": "${serverUrl}"}, + {"url": "https://github.com/facebook/react-native/releases/download/v1000.0.0/test.tar.gz"} + ], + "size": 0, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "path": "bar" + } + } +} +`; + await fs.writeFile(`${tmpDir}/entry-point`, dotslashContents); + + await expect( + writeReleaseAssetUrlsToDotSlashFile({ + filename: `${tmpDir}/entry-point`, + releaseTag: 'v1000.0.1', + }), + ).resolves.toBeUndefined(); + + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + + const updatedContents = await fs.readFile(`${tmpDir}/entry-point`, 'utf8'); + + expect(updatedContents).toMatchSnapshot('updated dotslash file'); + expect(signedsource.verifySignature(updatedContents)).toBe(true); + }); + + test('fails if upstream returns an incorrect asset', async () => { + const dotslashContents = `#!/usr/bin/env dotslash +// @${'generated SignedSource<<00000000000000000000000000000000>>'} +{ + "name": "test", + "platforms": { + "linux-x86_64": { + "providers": [ + {"url": "${serverUrl}"}, + ], + "size": 1, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "path": "bar" + } + } +} +`; + await fs.writeFile(`${tmpDir}/entry-point`, dotslashContents); + + await expect( + writeReleaseAssetUrlsToDotSlashFile({ + filename: `${tmpDir}/entry-point`, + releaseTag: 'v1001.0.0', + }), + ).rejects.toThrowErrorMatchingSnapshot(); + + expect(consoleLog.mock.calls).toMatchSnapshot('console.log calls'); + }); +}); diff --git a/scripts/releases/create-release-commit.js b/scripts/releases/create-release-commit.js index 50d2f8a8acd9..09d4f7cc888d 100644 --- a/scripts/releases/create-release-commit.js +++ b/scripts/releases/create-release-commit.js @@ -10,6 +10,9 @@ const {setVersion} = require('../releases/set-version'); const {getBranchName} = require('../releases/utils/scm-utils'); +const { + writeReleaseAssetUrlsToDotSlashFiles, +} = require('../releases/write-dotslash-release-asset-urls'); const {parseVersion} = require('./utils/version-utils'); const {execSync} = require('child_process'); const yargs = require('yargs'); @@ -49,6 +52,9 @@ async function main() { console.info('Setting version for monorepo packages and react-native'); await setVersion(version, false); // version, skip-react-native + console.info('Writing release asset URLs to DotSlash files'); + await writeReleaseAssetUrlsToDotSlashFiles(version); + if (dryRun) { console.info('Running in dry-run mode, skipping git commit'); console.info( diff --git a/scripts/releases/upload-release-assets-for-dotslash.js b/scripts/releases/upload-release-assets-for-dotslash.js new file mode 100644 index 000000000000..2d3006f5f061 --- /dev/null +++ b/scripts/releases/upload-release-assets-for-dotslash.js @@ -0,0 +1,395 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const {REPO_ROOT} = require('../shared/consts'); +const {getWithCurl} = require('./utils/curl-utils'); +const { + isHttpProvider, + processDotSlashFileInPlace, + validateDotSlashArtifactData, +} = require('./utils/dotslash-utils'); +const { + FIRST_PARTY_DOTSLASH_FILES, +} = require('./write-dotslash-release-asset-urls'); +const {Octokit} = require('@octokit/rest'); +const nullthrows = require('nullthrows'); +const path = require('path'); +const {parseArgs} = require('util'); + +/*:: +import type {DotSlashProvider, DotSlashHttpProvider, DotSlashArtifactInfo} from './utils/dotslash-utils'; +import type {IOctokit} from './utils/octokit-utils'; + +type GitHubReleaseAsset = {id: string, ...}; +type ReleaseAssetMap = $ReadOnlyMap; + +type ReleaseInfo = $ReadOnly<{ + releaseId: string, + releaseTag: string, + existingAssetsByName: ReleaseAssetMap, +}>; + +type ExecutionOptions = $ReadOnly<{ + force: boolean, + dryRun: boolean, +}>; +*/ + +async function main() { + const { + positionals: [version], + values: {help, token, releaseId, force, dryRun}, + } = parseArgs({ + allowPositionals: true, + options: { + token: {type: 'string'}, + releaseId: {type: 'string'}, + force: {type: 'boolean', default: false}, + dryRun: {type: 'boolean', default: false}, + help: {type: 'boolean'}, + }, + }); + + if (help) { + console.log(` + Usage: node ./scripts/releases/upload-release-assets-for-dotslash.js --release_id --token [--force] [--dry-run] + + Scans first-party DotSlash files in the repo for URLs referencing assets of + an upcoming release, and uploads the actual assets to the GitHub release + identified by the given release ID. + + Options: + The version of the release to upload assets for, with or + without the 'v' prefix. + --dry-run Do not upload release assets. + --force Overwrite existing release assets. + --release_id The ID of the GitHub release to upload assets to. + --token A GitHub token with write access to the release. +`); + return; + } + + if (version == null) { + throw new Error('Missing version argument'); + } + + await uploadReleaseAssetsForDotSlashFiles({ + version, + token, + releaseId, + force, + dryRun, + }); +} + +async function uploadReleaseAssetsForDotSlashFiles( + {version, token, releaseId, force = false, dryRun = false} /*: { + version: string, + token: string, + releaseId: string, + force?: boolean, + dryRun?: boolean, + } */, +) /*: Promise */ { + const releaseTag = version.startsWith('v') ? version : `v${version}`; + const octokit = new Octokit({auth: token}); + const existingAssetsByName = await getReleaseAssetMap( + { + releaseId, + }, + octokit, + ); + const releaseInfo = { + releaseId, + releaseTag, + existingAssetsByName, + }; + const executionOptions = { + force, + dryRun, + }; + for (const filename of FIRST_PARTY_DOTSLASH_FILES) { + await uploadReleaseAssetsForDotSlashFile( + filename, + releaseInfo, + executionOptions, + octokit, + ); + } +} + +/** + * List all release assets for a particular GitHub release ID, and return them + * as a map keyed by asset names. + */ +async function getReleaseAssetMap( + {releaseId} /*: { + releaseId: string, +} */, + octokit /*: IOctokit */, +) /*: Promise */ { + const existingAssets = await octokit.repos.listReleaseAssets({ + owner: 'facebook', + repo: 'react-native', + release_id: releaseId, + }); + return new Map(existingAssets.data.map(asset => [asset.name, asset])); +} + +/** + * Given a first-party DotSlash file path in the repo, reupload the referenced + * binaries from the upstream provider (typically: Meta CDN) to the draft + * release (hosted on GitHub). + */ +async function uploadReleaseAssetsForDotSlashFile( + filename /*: string */, + releaseInfo /*: ReleaseInfo */, + executionOptions /*: ExecutionOptions */, + octokit /*: IOctokit */, +) /*: Promise */ { + const fullPath = path.resolve(REPO_ROOT, filename); + console.log(`Uploading assets for ${filename}...`); + await processDotSlashFileInPlace( + fullPath, + async (providers, suggestedFilename, artifactInfo) => { + await fetchUpstreamAssetAndUploadToRelease( + { + providers, + suggestedFilename, + artifactInfo, + dotslashFilename: filename, + }, + releaseInfo, + executionOptions, + octokit, + ); + }, + ); +} + +/** + * Given a description of a DotSlash artifact for a particular platform, + * infers the upstream URL ( = where the binary is currently available) and + * release asset URL ( = where the binary will be hosted after the release), + * then downloads the asset from the the upstream URL and uploads it to GitHub + * at the desired URL. + */ +async function fetchUpstreamAssetAndUploadToRelease( + { + providers, + // NOTE: We mostly ignore suggestedFilename in favour of reading the actual asset URLs + suggestedFilename, + artifactInfo, + dotslashFilename, + } /*: { + providers: $ReadOnlyArray, + suggestedFilename: string, + artifactInfo: DotSlashArtifactInfo, + dotslashFilename: string, +} */, + releaseInfo /*: ReleaseInfo */, + executionOptions /*: ExecutionOptions */, + octokit /*: IOctokit */, +) { + const targetReleaseAssetInfo = providers + .map(provider => parseReleaseAssetInfo(provider, releaseInfo.releaseTag)) + .find(Boolean); + if (targetReleaseAssetInfo == null) { + console.log( + `[${suggestedFilename} (suggested)] DotSlash file does not reference any release URLs for this asset - ignoring.`, + ); + return; + } + const upstreamProvider /*: ?DotSlashHttpProvider */ = providers + .filter(isHttpProvider) + .find(provider => !parseReleaseAssetInfo(provider, releaseInfo.releaseTag)); + if (upstreamProvider == null) { + throw new Error( + `No upstream URL found for release asset ${targetReleaseAssetInfo.name}`, + ); + } + const existingAsset = releaseInfo.existingAssetsByName.get( + targetReleaseAssetInfo.name, + ); + if (existingAsset && !executionOptions.force) { + console.log( + `[${targetReleaseAssetInfo.name}] Skipping existing release asset...`, + ); + return; + } + await maybeDeleteExistingReleaseAsset( + { + name: targetReleaseAssetInfo.name, + existingAsset, + }, + executionOptions, + octokit, + ); + const {data, contentType} = await fetchAndValidateUpstreamAsset({ + name: targetReleaseAssetInfo.name, + url: upstreamProvider.url, + artifactInfo, + }); + if (executionOptions.dryRun) { + console.log( + `[${targetReleaseAssetInfo.name}] Dry run: Not uploading to release.`, + ); + return; + } + await uploadAndVerifyReleaseAsset( + { + name: targetReleaseAssetInfo.name, + url: targetReleaseAssetInfo.url, + data, + contentType, + releaseId: releaseInfo.releaseId, + dotslashFilename, + }, + octokit, + ); +} + +/** + * Checks whether the given DotSlash artifact provider refers to an asset URL + * that is part of the current release. Returns the asset name as well as the + * full URL if that is the case. Returns null otherwise. + */ +function parseReleaseAssetInfo( + provider /*: DotSlashProvider */, + releaseTag /*: string */, +) /*: + ?{ + name: string, + url: string, + } +*/ { + const releaseAssetPrefix = `https://github.com/facebook/react-native/releases/download/${encodeURIComponent(releaseTag)}/`; + + if (isHttpProvider(provider) && provider.url.startsWith(releaseAssetPrefix)) { + return { + name: decodeURIComponent(provider.url.slice(releaseAssetPrefix.length)), + url: provider.url, + }; + } + return null; +} + +/** + * Deletes the specified release asset if it exists, unless we are in dry run + * mode (in which case this is a noop). + */ +async function maybeDeleteExistingReleaseAsset( + {name, existingAsset} /*: { + name: string, + existingAsset: ?GitHubReleaseAsset, +} +*/, + {dryRun} /*: ExecutionOptions */, + octokit /*: IOctokit */, +) /*: Promise */ { + if (!existingAsset) { + return; + } + if (dryRun) { + console.log(`[${name}] Dry run: Not deleting existing release asset.`); + return; + } + console.log(`[${name}] Deleting existing release asset...`); + await octokit.repos.deleteReleaseAsset({ + owner: 'facebook', + repo: 'react-native', + asset_id: existingAsset.id, + }); +} + +/** + * Given a description of a DotSlash artifact, downloads it and verifies its + * size and hash (similarly to how DotSlash itself would do it after release). + */ +async function fetchAndValidateUpstreamAsset( + {name, url, artifactInfo} /*: { + name: string, + url: string, + artifactInfo: DotSlashArtifactInfo, +} */, +) /*: Promise<{ + data: Buffer, + contentType: string, +}> */ { + console.log(`[${name}] Downloading from ${url}...`); + // NOTE: Using curl because we have seen issues with fetch() on GHA + // and the Meta CDN. ¯\_(ツ)_/¯ + const {data, contentType} = await getWithCurl(url); + console.log(`[${name}] Validating download...`); + await validateDotSlashArtifactData(data, artifactInfo); + return { + data, + contentType: contentType ?? 'application/octet-stream', + }; +} + +/** + * Uploads the specified asset to a GitHub release. + * + * By the time we call this function, we have already commited (and published!) + * a reference to the asset's eventual URL, so we also verify that the URL path + * hasn't changed in the process. + */ +async function uploadAndVerifyReleaseAsset( + {name, data, contentType, url, releaseId, dotslashFilename} /*: { + name: string, + data: Buffer, + contentType: string, + url: string, + releaseId: string, + dotslashFilename: string, +} +*/, + octokit /*: IOctokit */, +) /*: Promise */ { + console.log(`[${name}] Uploading to release...`); + const { + data: {browser_download_url}, + } = await octokit.repos.uploadReleaseAsset({ + owner: 'facebook', + repo: 'react-native', + release_id: releaseId, + name, + data, + headers: { + 'content-type': contentType, + }, + }); + + // Once uploaded, check that the name didn't get mangled. + const actualUrlPathname = new URL(browser_download_url).pathname; + const actualAssetName = decodeURIComponent( + nullthrows(/[^/]*$/.exec(actualUrlPathname))[0], + ); + if (actualAssetName !== name) { + throw new Error( + `Asset name was changed while uploading to the draft release: expected ${name}, got ${actualAssetName}. ` + + `${dotslashFilename} has already been published to npm with the following URL, which will not work when the release is published on GitHub: ${url}`, + ); + } + console.log(`[${name}] Uploaded to ${browser_download_url}`); +} + +module.exports = { + uploadReleaseAssetsForDotSlashFiles, + getReleaseAssetMap, + uploadReleaseAssetsForDotSlashFile, +}; + +if (require.main === module) { + void main(); +} diff --git a/scripts/releases/utils/__tests__/__snapshots__/dotslash-utils-test.js.snap b/scripts/releases/utils/__tests__/__snapshots__/dotslash-utils-test.js.snap new file mode 100644 index 000000000000..d090e4b0b447 --- /dev/null +++ b/scripts/releases/utils/__tests__/__snapshots__/dotslash-utils-test.js.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`processDotSlashFileInPlace comments, multiple platforms, providers + replacement: contents after processing 1`] = ` +"#!/usr/bin/env dotslash +// Top-level comment +{ + \\"name\\": \\"test\\", + \\"platforms\\": { + // Comment on linux-x86_64 + \\"linux-x86_64\\": { + \\"size\\": 0, + \\"hash\\": \\"sha256\\", + \\"digest\\": \\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\\", + \\"providers\\": [ + { + \\"url\\": \\"https://example.com/replaced/test-linux-x86_64.tar.gz\\" + } + ], + \\"format\\": \\"tar.gz\\", + \\"path\\": \\"bar\\" + }, + // Comment on macos-aarch64 + \\"macos-aarch64\\": { + \\"size\\": 0, + \\"hash\\": \\"sha256\\", + \\"digest\\": \\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\\", + \\"providers\\": [ + { + \\"url\\": \\"https://primary.example.com/foo-mac.zip\\", + \\"weight\\": 3 + }, + { + \\"url\\": \\"https://mirror1.example.com/foo-mac.zip\\", + \\"weight\\": 1 + }, + { + \\"url\\": \\"https://example.com/added/test-macos-aarch64.zip\\" + } + ], + \\"format\\": \\"zip\\", + \\"path\\": \\"bar\\", + } + } +}" +`; + +exports[`processDotSlashFileInPlace comments, multiple platforms, providers + replacement: transformProviders calls 1`] = ` +Array [ + Array [ + Array [ + Object { + "url": "https://primary.example.com/foo-linux.tar.gz", + "weight": 3, + }, + Object { + "url": "https://mirror1.example.com/foo-linux.tar.gz", + "weight": 1, + }, + Object { + "url": "https://mirror2.example.com/foo-linux.tar.gz", + "weight": 1, + }, + Object { + "url": "https://mirror3.example.com/foo-linux.tar.gz", + "weight": 1, + }, + ], + "test-linux-x86_64.tar.gz", + Object { + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "hash": "sha256", + "size": 0, + }, + ], + Array [ + Array [ + Object { + "url": "https://primary.example.com/foo-mac.zip", + "weight": 3, + }, + Object { + "url": "https://mirror1.example.com/foo-mac.zip", + "weight": 1, + }, + ], + "test-macos-aarch64.zip", + Object { + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "hash": "sha256", + "size": 0, + }, + ], +] +`; + +exports[`validateDotSlashArtifactData blake3 failure on digest mismatch 1`] = `"blake3 mismatch: expected 2623f14eac39a9cc7b211cda9c52bcb9949ccd63aed4040a6a1a9f5f9b9431fa, got af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"`; + +exports[`validateDotSlashArtifactData blake3 failure on size mismatch 1`] = `"size mismatch: expected 1, got 0"`; + +exports[`validateDotSlashArtifactData sha256 failure on digest mismatch 1`] = `"sha256 mismatch: expected 558b2587b199594ac439b9464e14ea72429bf6998c4fbfa941c1cf89244c0b3e, got e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"`; + +exports[`validateDotSlashArtifactData sha256 failure on size mismatch 1`] = `"size mismatch: expected 1, got 0"`; diff --git a/scripts/releases/utils/__tests__/curl-utils-test.js b/scripts/releases/utils/__tests__/curl-utils-test.js new file mode 100644 index 000000000000..77592e53fdbe --- /dev/null +++ b/scripts/releases/utils/__tests__/curl-utils-test.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const {getWithCurl} = require('../curl-utils'); +const http = require('http'); + +let server, serverUrl; + +beforeEach(async () => { + await new Promise((resolve, reject) => { + server = http.createServer((req, res) => { + if (req.url !== '/') { + res.writeHead(404); + res.end(); + return; + } + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Hello World\n'); + }); + server.on('error', reject); + server.listen(0, 'localhost', () => { + const {port} = server.address(); + serverUrl = `http://localhost:${port}`; + resolve(); + }); + }); +}); + +afterEach(async () => { + await new Promise((resolve, reject) => { + server.close(err => { + if (err) { + reject(err); + } + resolve(); + }); + }); +}); + +describe('getWithCurl', () => { + test('success', async () => { + await expect(getWithCurl(serverUrl)).resolves.toEqual({ + data: Buffer.from('Hello World\n'), + contentType: 'text/plain', + }); + }); + + test('fails on 404', async () => { + await expect(getWithCurl(serverUrl + '/error')).rejects.toThrowError(); + }); +}); diff --git a/scripts/releases/utils/__tests__/dotslash-utils-test.js b/scripts/releases/utils/__tests__/dotslash-utils-test.js new file mode 100644 index 000000000000..321ece59bc37 --- /dev/null +++ b/scripts/releases/utils/__tests__/dotslash-utils-test.js @@ -0,0 +1,267 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const { + dangerouslyResignGeneratedFile, + processDotSlashFileInPlace, + validateAndParseDotSlashFile, + validateDotSlashArtifactData, +} = require('../dotslash-utils'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +jest.useRealTimers(); + +let tmpDir: string; + +beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dotslash-utils-test-')); +}); + +afterEach(() => { + fs.rmSync(tmpDir, {recursive: true}); +}); + +describe('validateAndParseDotSlashFile', () => { + test('succeeds on a minimal valid DotSlash file', async () => { + const contents = `#!/usr/bin/env dotslash +{ + "name": "test", + "platforms": {} +}`; + await fs.promises.writeFile(`${tmpDir}/entry-point`, contents); + await expect( + validateAndParseDotSlashFile(`${tmpDir}/entry-point`), + ).resolves.toEqual({ + name: 'test', + platforms: {}, + }); + }); +}); + +describe('processDotSlashFileInPlace', () => { + test('succeeds on a minimal valid DotSlash file', async () => { + const transformProviders = jest.fn(); + const contentsBefore = `#!/usr/bin/env dotslash +{ + "name": "test", + "platforms": {} +}`; + await fs.promises.writeFile(`${tmpDir}/entry-point`, contentsBefore); + await processDotSlashFileInPlace( + `${tmpDir}/entry-point`, + transformProviders, + ); + expect(transformProviders).not.toHaveBeenCalled(); + expect(fs.readFileSync(`${tmpDir}/entry-point`, 'utf8')).toBe( + contentsBefore, + ); + }); + + test('comments, multiple platforms, providers + replacement', async () => { + const transformProviders = jest.fn(); + const contentsBefore = `#!/usr/bin/env dotslash +// Top-level comment +{ + "name": "test", + "platforms": { + // Comment on linux-x86_64 + "linux-x86_64": { + "size": 0, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "providers": [ + {"url": "https://primary.example.com/foo-linux.tar.gz", "weight": 3}, + {"url": "https://mirror1.example.com/foo-linux.tar.gz", "weight": 1}, + {"url": "https://mirror2.example.com/foo-linux.tar.gz", "weight": 1}, + {"url": "https://mirror3.example.com/foo-linux.tar.gz", "weight": 1} + ], + "format": "tar.gz", + "path": "bar" + }, + // Comment on macos-aarch64 + "macos-aarch64": { + "size": 0, + "hash": "sha256", + "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "providers": [ + {"url": "https://primary.example.com/foo-mac.zip", "weight": 3}, + {"url": "https://mirror1.example.com/foo-mac.zip", "weight": 1}, + ], + "format": "zip", + "path": "bar", + } + } +}`; + fs.writeFileSync(`${tmpDir}/entry-point`, contentsBefore); + transformProviders.mockImplementationOnce( + (providers, suggestedFilename) => { + return [ + { + url: + 'https://example.com/replaced/' + + encodeURIComponent(suggestedFilename), + }, + ]; + }, + ); + transformProviders.mockImplementationOnce( + (providers, suggestedFilename) => { + return [ + ...providers, + { + url: + 'https://example.com/added/' + + encodeURIComponent(suggestedFilename), + }, + ]; + }, + ); + await processDotSlashFileInPlace( + `${tmpDir}/entry-point`, + transformProviders, + ); + expect(transformProviders.mock.calls).toMatchSnapshot( + 'transformProviders calls', + ); + expect(fs.readFileSync(`${tmpDir}/entry-point`, 'utf8')).toMatchSnapshot( + 'contents after processing', + ); + }); + + test('fails on an invalid DotSlash file (no shebang line)', async () => { + const transformProviders = jest.fn(); + const contentsBefore = `{ + "name": "test", + "platforms": {} +}`; + fs.writeFileSync(`${tmpDir}/entry-point`, contentsBefore); + await expect( + processDotSlashFileInPlace(`${tmpDir}/entry-point`, transformProviders), + ).rejects.toThrow(); + expect(transformProviders).not.toHaveBeenCalled(); + expect(fs.readFileSync(`${tmpDir}/entry-point`, 'utf8')).toBe( + contentsBefore, + ); + }); + + test('fails on an invalid DotSlash file (no platforms)', async () => { + const transformProviders = jest.fn(); + const contentsBefore = `#!/usr/bin/env dotslash +{ + "name": "test" +}`; + fs.writeFileSync(`${tmpDir}/entry-point`, contentsBefore); + await expect( + processDotSlashFileInPlace(`${tmpDir}/entry-point`, transformProviders), + ).rejects.toThrow(); + expect(transformProviders).not.toHaveBeenCalled(); + expect(fs.readFileSync(`${tmpDir}/entry-point`, 'utf8')).toBe( + contentsBefore, + ); + }); +}); + +describe('dangerouslyResignGeneratedFile', () => { + test('successfully re-signs a file', async () => { + const contentsBefore = `#!/usr/bin/env dotslash +// @${'generated SignedSource<<00000000000000000000000000000000' + '>>'} +{ + "name": "test", + "platforms": {} +}`; + fs.writeFileSync(`${tmpDir}/entry-point`, contentsBefore); + await dangerouslyResignGeneratedFile(`${tmpDir}/entry-point`); + expect(fs.readFileSync(`${tmpDir}/entry-point`, 'utf8')) + .toBe(`#!/usr/bin/env dotslash +// @${'generated SignedSource<<5ccb2839bdbd070dffcda52c6aa922a3' + '>>'} +{ + "name": "test", + "platforms": {} +}`); + }); +}); + +describe('validateDotSlashArtifactData', () => { + test('blake3 success', async () => { + await expect( + validateDotSlashArtifactData(Buffer.from([]), { + hash: 'blake3', + digest: + 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262', + size: 0, + }), + ).resolves.toBeUndefined(); + }); + + test('blake3 failure on size mismatch', async () => { + await expect( + validateDotSlashArtifactData(Buffer.from([]), { + hash: 'blake3', + digest: + 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262', + size: 1, + }), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('blake3 failure on digest mismatch', async () => { + await expect( + validateDotSlashArtifactData(Buffer.from([]), { + hash: 'blake3', + digest: + 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262' + .split('') + .reverse() + .join(''), + size: 0, + }), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('sha256 success', async () => { + await expect( + validateDotSlashArtifactData(Buffer.from([]), { + hash: 'sha256', + digest: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + size: 0, + }), + ).resolves.toBeUndefined(); + }); + + test('sha256 failure on size mismatch', async () => { + await expect( + validateDotSlashArtifactData(Buffer.from([]), { + hash: 'sha256', + digest: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + size: 1, + }), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('sha256 failure on digest mismatch', async () => { + await expect( + validateDotSlashArtifactData(Buffer.from([]), { + hash: 'sha256', + digest: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + .split('') + .reverse() + .join(''), + size: 0, + }), + ).rejects.toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/scripts/releases/utils/curl-utils.js b/scripts/releases/utils/curl-utils.js new file mode 100644 index 000000000000..6b39eb79b36f --- /dev/null +++ b/scripts/releases/utils/curl-utils.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const spawnAsync = require('@expo/spawn-async'); +const {promises: fs} = require('fs'); +const os = require('os'); +const path = require('path'); + +/*:: +type CurlResult = { + data: Buffer, + contentType?: string, +}; +*/ + +async function getWithCurl(url /*: string */) /*: Promise */ { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'get-with-curl-')); + const tempFile = path.join(tempDir, 'data'); + try { + const { + output: [curlStdout], + } = await spawnAsync( + 'curl', + [ + '--silent', + '--location', + '--output', + tempFile, + url, + '--write-out', + '%{content_type}', + '--fail', + ], + {encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']}, + ); + const data = await fs.readFile(tempFile); + const contentType = curlStdout.trim(); + if (contentType === '') { + return {data}; + } + return {data, contentType}; + } finally { + await fs.rm(tempDir, {recursive: true, force: true}); + } +} + +function getTempDirPatternForTests() /*: RegExp */ { + return new RegExp( + escapeRegex(path.join(os.tmpdir(), 'get-with-curl-')) + + '.[^\\s' + + escapeRegex(path.sep) + + ']+', + 'g', + ); +} + +function escapeRegex(str /*: string */) /*: string */ { + return str.replace(/[-[\]\\/{}()*+?.^$|]/g, '\\$&'); +} + +module.exports = { + getWithCurl, + getTempDirPatternForTests, +}; diff --git a/scripts/releases/utils/dotslash-utils.js b/scripts/releases/utils/dotslash-utils.js new file mode 100644 index 000000000000..eb435fffd71f --- /dev/null +++ b/scripts/releases/utils/dotslash-utils.js @@ -0,0 +1,214 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const dotslash = require('fb-dotslash'); +const {promises: fs} = require('fs'); +const {applyEdits, modify, parse} = require('jsonc-parser'); +const os = require('os'); +const path = require('path'); +const signedsource = require('signedsource'); +const execFile = require('util').promisify(require('child_process').execFile); + +/*:: +export type DotSlashHttpProvider = { + type?: 'http', + url: string, +}; + +export type DotSlashProvider = DotSlashHttpProvider | { + type: 'github-release', + repo: string, + tag: string, + name: string, +}; + +type DotSlashPlatformSpec = { + providers: DotSlashProvider[], + hash: 'blake3' | 'sha256', + digest: string, + size: number, + format?: string, + ... +}; + +export type DotSlashArtifactInfo = $ReadOnly<{ + size: number, + hash: 'blake3' | 'sha256', + digest: string, + ... +}>; + +type JSONCFormattingOptions = { + tabSize?: number, + insertSpaces?: boolean, + eol?: string, +}; + +type DotSlashProvidersTransformFn = ( + providers: $ReadOnlyArray, + suggestedFilename: string, + artifactInfo: DotSlashArtifactInfo, +) => ?$ReadOnlyArray | Promise>; +*/ + +const DEFAULT_FORMATTING_OPTIONS /*: $ReadOnly */ = { + tabSize: 4, + insertSpaces: true, + eol: '\n', +}; + +/** + * Process a DotSlash file and call a callback with the providers for each platform. + * The callback can return a new providers array to update the file. + * The function will preserve formatting and comments in the file (except any comments + * that are within the providers array). + */ +async function processDotSlashFileInPlace( + filename /*: string */, + transformProviders /*: DotSlashProvidersTransformFn */, + formattingOptions /*: $ReadOnly */ = DEFAULT_FORMATTING_OPTIONS, +) /*: Promise */ { + // Validate the file using `dotslash` itself so we can be reasonably sure that it conforms + // to the expected format. + await validateAndParseDotSlashFile(filename); + + const originalContents = await fs.readFile(filename, 'utf-8'); + const [shebang, originalContentsJson] = + splitShebangFromContents(originalContents); + const json = parse(originalContentsJson); + let intermediateContentsJson = originalContentsJson; + for (const [platform, platformSpec] of Object.entries(json.platforms) /*:: + as $ReadOnlyArray<[string, DotSlashPlatformSpec]> + */) { + const providers = platformSpec.providers; + const suggestedFilename = + `${sanitizeFileNameComponent(json.name)}-${platform}` + + (platformSpec.format != null ? `.${platformSpec.format}` : ''); + const {hash, digest, size} = platformSpec; + const newProviders = + (await transformProviders(providers, suggestedFilename, { + hash, + digest, + size, + })) ?? providers; + if (newProviders !== providers) { + const edits = modify( + intermediateContentsJson, + ['platforms', platform, 'providers'], + newProviders, + { + formattingOptions, + }, + ); + intermediateContentsJson = applyEdits(intermediateContentsJson, edits); + } + } + if (originalContentsJson !== intermediateContentsJson) { + await fs.writeFile(filename, shebang + intermediateContentsJson); + // Validate the modified file to make sure we haven't broken it. + await validateAndParseDotSlashFile(filename); + } +} + +function sanitizeFileNameComponent( + fileNameComponent /*: string */, +) /*: string */ { + return fileNameComponent.replace(/[^a-zA-Z0-9.]/g, '.'); +} + +function splitShebangFromContents( + contents /*: string */, +) /*: [string, string] */ { + const shebangMatch = contents.match(/^#!.*\n/); + const shebang = shebangMatch ? shebangMatch[0] : ''; + const contentsWithoutShebang = shebang + ? contents.substring(shebang.length) + : contents; + return [shebang, contentsWithoutShebang]; +} + +/** + * Validate a DotSlash file and return its parsed contents. + * Throws an error if the file is not valid. + * + * See https://dotslash-cli.com/docs/dotslash-file/ + */ +async function validateAndParseDotSlashFile( + filename /*: string */, +) /*: mixed */ { + const {stdout} = await execFile(dotslash, ['--', 'parse', filename]); + return JSON.parse(stdout); +} + +/** + * Re-sign a file previously signed with `signedsource`. Use with caution. + */ +async function dangerouslyResignGeneratedFile( + filename /*: string */, +) /*: Promise */ { + const GENERATED = '@' + 'generated'; + const PATTERN = new RegExp(`${GENERATED} (?:SignedSource<<([a-f0-9]{32})>>)`); + const originalContents = await fs.readFile(filename, 'utf-8'); + + const newContents = signedsource.signFile( + originalContents.replace(PATTERN, signedsource.getSigningToken()), + ); + await fs.writeFile(filename, newContents); +} + +/** + * Checks that the given buffer matches the given hash and size. This is + * equivalent to the validation that DotSlash performs after fetching a blob + * and before extracting/executing it. + */ +async function validateDotSlashArtifactData( + data /*: Buffer */, + artifactInfo /*: DotSlashArtifactInfo */, +) /*: Promise */ { + const {digest: expectedDigest, hash, size} = artifactInfo; + if (data.length !== size) { + throw new Error(`size mismatch: expected ${size}, got ${data.length}`); + } + const hashFunction = hash === 'blake3' ? 'b3sum' : 'sha256'; + + const tempDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'validate-artifact-hash-'), + ); + try { + const tempFile = path.join(tempDir, 'data'); + await fs.writeFile(tempFile, data); + const {stdout} = await execFile(dotslash, ['--', hashFunction, tempFile]); + const actualDigest = stdout.trim(); + if (actualDigest !== expectedDigest) { + throw new Error( + `${hash} mismatch: expected ${expectedDigest}, got ${actualDigest}`, + ); + } + } finally { + await fs.rm(tempDir, {recursive: true, force: true}); + } +} + +function isHttpProvider( + provider /*: DotSlashProvider */, +) /*: implies provider is DotSlashHttpProvider */ { + return provider.type === 'http' || provider.type == null; +} + +module.exports = { + DEFAULT_FORMATTING_OPTIONS, + dangerouslyResignGeneratedFile, + isHttpProvider, + processDotSlashFileInPlace, + validateAndParseDotSlashFile, + validateDotSlashArtifactData, +}; diff --git a/scripts/releases/utils/octokit-utils.js b/scripts/releases/utils/octokit-utils.js new file mode 100644 index 000000000000..ce5f7e07be4e --- /dev/null +++ b/scripts/releases/utils/octokit-utils.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +// An interface shaped like a subset of the Octokit class from `@octokit/rest`. +// Used to allow mocking in tests. +export interface IOctokit { + +repos: $ReadOnly<{ + listReleaseAssets: ( + params: $ReadOnly<{ + owner: string, + repo: string, + release_id: string, + }>, + ) => Promise<{ + data: Array<{ + id: string, + name: string, + ... + }>, + ... + }>, + uploadReleaseAsset: ( + params: $ReadOnly<{ + owner: string, + repo: string, + release_id: string, + name: string, + data: Buffer, + headers: $ReadOnly<{ + 'content-type': string, + ... + }>, + ... + }>, + ) => Promise<{ + data: { + browser_download_url: string, + ... + }, + ... + }>, + deleteReleaseAsset: (params: { + owner: string, + repo: string, + asset_id: string, + ... + }) => Promise, + }>; +} diff --git a/scripts/releases/validate-dotslash-artifacts.js b/scripts/releases/validate-dotslash-artifacts.js new file mode 100644 index 000000000000..7c4841341b63 --- /dev/null +++ b/scripts/releases/validate-dotslash-artifacts.js @@ -0,0 +1,99 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const {REPO_ROOT} = require('../shared/consts'); +const {getWithCurl} = require('./utils/curl-utils'); +const { + isHttpProvider, + processDotSlashFileInPlace, + validateDotSlashArtifactData, +} = require('./utils/dotslash-utils'); +const { + FIRST_PARTY_DOTSLASH_FILES, +} = require('./write-dotslash-release-asset-urls'); +const path = require('path'); +const {parseArgs, styleText} = require('util'); + +async function main() { + const { + positionals: [], + values: {help}, + } = parseArgs({ + allowPositionals: true, + options: { + help: {type: 'boolean'}, + }, + }); + + if (help) { + console.log(` + Usage: node ./scripts/releases/validate-dotslash-artifacts.js + + Ensures that the first-party DotSlash files in the current commit all point to + valid URLs that return the described artifacts. This script is intended to run + in two key scenarios: + + 1. Continuously on main - this verifies the output of the Meta-internal CI pipeline + that publishes DotSlash files to the repo. + 2. After a release is published - this verifies the behavior of the + write-dotslash-release-asset-urls.js and upload-release-assets-for-dotslash.js + scripts, as well as any commits (e.g. merges, picks) that touched the DotSlash + files in the release branch since the branch was cut. + Release asset URLs are only valid once the release is published, so we can't + run this continuously on commits in the release branch (specifically, it would + fail on the release commit itself). +`); + return; + } + + await validateDotSlashArtifacts(); +} + +async function validateDotSlashArtifacts() /*: Promise */ { + for (const filename of FIRST_PARTY_DOTSLASH_FILES) { + const fullPath = path.join(REPO_ROOT, filename); + console.log(`Validating all HTTP providers for ${filename}...`); + await processDotSlashFileInPlace( + fullPath, + async (providers, suggestedFilename, artifactInfo) => { + for (const provider of providers) { + if (!isHttpProvider(provider)) { + console.log( + styleText( + 'dim', + ` `, + ), + ); + continue; + } + console.log( + styleText( + 'dim', + ` ${provider.url} (expected ${artifactInfo.size} bytes, ${artifactInfo.hash} ${artifactInfo.digest})`, + ), + ); + const {data} = await getWithCurl(provider.url); + await validateDotSlashArtifactData(data, artifactInfo); + } + return providers; + }, + ); + } +} + +module.exports = { + validateDotSlashArtifacts, +}; + +if (require.main === module) { + void main(); +} diff --git a/scripts/releases/write-dotslash-release-asset-urls.js b/scripts/releases/write-dotslash-release-asset-urls.js new file mode 100644 index 000000000000..8d09f26e85a9 --- /dev/null +++ b/scripts/releases/write-dotslash-release-asset-urls.js @@ -0,0 +1,171 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +/*:: +import type {DotSlashHttpProvider, DotSlashProvider, DotSlashArtifactInfo} from './utils/dotslash-utils'; +*/ + +const {REPO_ROOT} = require('../shared/consts'); +const {getWithCurl} = require('./utils/curl-utils'); +const { + dangerouslyResignGeneratedFile, + isHttpProvider, + processDotSlashFileInPlace, + validateAndParseDotSlashFile, + validateDotSlashArtifactData, +} = require('./utils/dotslash-utils'); +const {diff: jestDiff} = require('jest-diff'); +const path = require('path'); +const {parseArgs} = require('util'); + +const FIRST_PARTY_DOTSLASH_FILES = [ + 'packages/debugger-shell/bin/react-native-devtools', +]; + +async function main() { + const { + positionals: [version], + values: {help}, + } = parseArgs({ + allowPositionals: true, + options: { + help: {type: 'boolean'}, + }, + }); + + if (help) { + console.log(` + Usage: node ./scripts/releases/write-dotslash-release-asset-urls.js + + Inserts references to release assets URLs into first-party DotSlash files in + the repo, in preparation for publishing a new release and uploading the + assets (which happens in a separate step). +`); + return; + } + + if (version == null) { + throw new Error('Missing version argument'); + } + + await writeReleaseAssetUrlsToDotSlashFiles(version); +} + +async function writeReleaseAssetUrlsToDotSlashFiles( + version /*: string */, +) /*: Promise */ { + const releaseTag = version.startsWith('v') ? version : `v${version}`; + for (const filename of FIRST_PARTY_DOTSLASH_FILES) { + await writeReleaseAssetUrlsToDotSlashFile({ + filename, + releaseTag, + }); + } +} + +async function writeReleaseAssetUrlsToDotSlashFile( + {filename, releaseTag} /*: {filename: string, releaseTag: string} */, +) /*: Promise */ { + const fullPath = path.resolve(REPO_ROOT, filename); + console.log(`Updating ${filename}...`); + await processDotSlashFileInPlace( + fullPath, + async (originalProviders, suggestedFilename, artifactInfo) => { + const updatedProviders = await updateAndVerifyProviders({ + providers: originalProviders, + suggestedFilename, + artifactInfo, + releaseTag, + }); + console.log( + 'Providers:\n', + diffProviderArrays(originalProviders, updatedProviders), + ); + return updatedProviders; + }, + ); + await dangerouslyResignGeneratedFile(fullPath); + await validateAndParseDotSlashFile(fullPath); +} + +async function updateAndVerifyProviders( + {providers: providersArg, suggestedFilename, artifactInfo, releaseTag} /*: + {providers: $ReadOnlyArray, + suggestedFilename: string, + artifactInfo: DotSlashArtifactInfo, + releaseTag: string,} +*/, +) { + const providers = providersArg.filter( + provider => !isPreviousReleaseAssetProvider(provider), + ); + const upstreamHttpProviders = providers.filter(isHttpProvider); + if (upstreamHttpProviders.length === 0) { + throw new Error( + 'No upstream HTTP providers found for asset: ' + suggestedFilename, + ); + } + for (const provider of upstreamHttpProviders) { + console.log(`Downloading from ${provider.url} for integrity validation...`); + const {data} = await getWithCurl(provider.url); + await validateDotSlashArtifactData(data, artifactInfo); + } + providers.unshift( + createReleaseAssetProvider({ + releaseTag, + suggestedFilename, + }), + ); + return providers; +} + +function isPreviousReleaseAssetProvider( + provider /*: DotSlashProvider */, +) /*: boolean */ { + return ( + isHttpProvider(provider) && + provider.url.startsWith( + 'https://github.com/facebook/react-native/releases/download/', + ) + ); +} + +function createReleaseAssetProvider( + { + releaseTag, + suggestedFilename, + } /*: {releaseTag: string, suggestedFilename: string} */, +) /*: DotSlashProvider */ { + return { + url: `https://github.com/facebook/react-native/releases/download/${encodeURIComponent(releaseTag)}/${encodeURIComponent(suggestedFilename)}`, + }; +} + +function diffProviderArrays( + original /*: $ReadOnlyArray */, + updated /*: $ReadOnlyArray */, +) { + return jestDiff(original, updated, { + aAnnotation: 'Original', + bAnnotation: 'Updated', + }); +} + +module.exports = { + FIRST_PARTY_DOTSLASH_FILES, + writeReleaseAssetUrlsToDotSlashFiles, + writeReleaseAssetUrlsToDotSlashFile, +}; + +if (require.main === module) { + void main(); +} diff --git a/yarn.lock b/yarn.lock index b618a2c8bc4a..033115077295 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.27.1": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== @@ -65,6 +65,17 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" +"@babel/generator@^7.26.9": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.25.9", "@babel/helper-annotate-as-pure@^7.27.1": version "7.27.3" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" @@ -223,6 +234,13 @@ dependencies: "@babel/types" "^7.28.0" +"@babel/parser@^7.26.9", "@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" @@ -1016,7 +1034,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.25.0", "@babel/template@^7.25.9", "@babel/template@^7.27.2", "@babel/template@^7.3.3": +"@babel/template@^7.25.0", "@babel/template@^7.25.9", "@babel/template@^7.26.9", "@babel/template@^7.27.2", "@babel/template@^7.3.3": version "7.27.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== @@ -1025,7 +1043,20 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.8", "@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.8": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a" + integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.9" + "@babel/parser" "^7.26.9" + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.9" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": version "7.28.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== @@ -1046,6 +1077,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@babel/types@^7.26.9", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1192,6 +1231,13 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@expo/spawn-async@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.7.2.tgz#fcfe66c3e387245e72154b1a7eae8cada6a47f58" + integrity sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew== + dependencies: + cross-spawn "^7.0.3" + "@fastify/busboy@^2.0.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" @@ -1595,6 +1641,11 @@ resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.2.tgz#68a486714d7a7fd1df56cb9bc89a860a0de866de" integrity sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw== +"@octokit/auth-token@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-6.0.0.tgz#b02e9c08a2d8937df09a2a981f226ad219174c53" + integrity sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w== + "@octokit/core@^5.0.2": version "5.2.1" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.2.1.tgz#58c21a5f689ee81e0b883b5aa77573a7ff1b4ea1" @@ -1621,6 +1672,19 @@ before-after-hook "^3.0.2" universal-user-agent "^7.0.0" +"@octokit/core@^7.0.2": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-7.0.3.tgz#0b5288995fed66920128d41cfeea34979d48a360" + integrity sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ== + dependencies: + "@octokit/auth-token" "^6.0.0" + "@octokit/graphql" "^9.0.1" + "@octokit/request" "^10.0.2" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + before-after-hook "^4.0.0" + universal-user-agent "^7.0.0" + "@octokit/endpoint@^10.1.4": version "10.1.4" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.4.tgz#8783be38a32b95af8bcb6523af20ab4eed7a2adb" @@ -1629,6 +1693,14 @@ "@octokit/types" "^14.0.0" universal-user-agent "^7.0.2" +"@octokit/endpoint@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-11.0.0.tgz#189fcc022721b4c49d0307eea6be3de1cfb53026" + integrity sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ== + dependencies: + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.2" + "@octokit/endpoint@^9.0.6": version "9.0.6" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.6.tgz#114d912108fe692d8b139cfe7fc0846dfd11b6c0" @@ -1655,6 +1727,15 @@ "@octokit/types" "^14.0.0" universal-user-agent "^7.0.0" +"@octokit/graphql@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-9.0.1.tgz#eb258fc9981403d2d751720832652c385b6c1613" + integrity sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg== + dependencies: + "@octokit/request" "^10.0.2" + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.0" + "@octokit/openapi-types@^24.2.0": version "24.2.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-24.2.0.tgz#3d55c32eac0d38da1a7083a9c3b0cca77924f7d3" @@ -1665,6 +1746,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-25.0.0.tgz#adeead36992abf966e89dcd53518d8b0dc910e0d" integrity sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw== +"@octokit/openapi-types@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-25.1.0.tgz#5a72a9dfaaba72b5b7db375fd05e90ca90dc9682" + integrity sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA== + "@octokit/plugin-paginate-rest@11.4.4-cjs.2": version "11.4.4-cjs.2" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz#979a10d577bce7a393e8e65953887e42b0a05000" @@ -1679,6 +1765,13 @@ dependencies: "@octokit/types" "^13.10.0" +"@octokit/plugin-paginate-rest@^13.0.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz#ca5bb1c7b85a583691263c1f788f607e9bcb74b3" + integrity sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw== + dependencies: + "@octokit/types" "^14.1.0" + "@octokit/plugin-request-log@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz#98a3ca96e0b107380664708111864cb96551f958" @@ -1689,6 +1782,11 @@ resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz#ccb75d9705de769b2aa82bcd105cc96eb0c00f69" integrity sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw== +"@octokit/plugin-request-log@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz#de1c1e557df6c08adb631bf78264fa741e01b317" + integrity sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q== + "@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1": version "13.3.2-cjs.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz#d0a142ff41d8f7892b6ccef45979049f51ecaa8d" @@ -1703,6 +1801,13 @@ dependencies: "@octokit/types" "^13.10.0" +"@octokit/plugin-rest-endpoint-methods@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz#ba30ca387fc2ac8bd93cf9f951174736babebd97" + integrity sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g== + dependencies: + "@octokit/types" "^14.1.0" + "@octokit/request-error@^5.1.1": version "5.1.1" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.1.1.tgz#b9218f9c1166e68bb4d0c89b638edc62c9334805" @@ -1719,6 +1824,24 @@ dependencies: "@octokit/types" "^14.0.0" +"@octokit/request-error@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-7.0.0.tgz#48ae2cd79008315605d00e83664891a10a5ddb97" + integrity sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg== + dependencies: + "@octokit/types" "^14.0.0" + +"@octokit/request@^10.0.2": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-10.0.3.tgz#2ffdb88105ce20d25dcab8a592a7040ea48306c7" + integrity sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA== + dependencies: + "@octokit/endpoint" "^11.0.0" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + fast-content-type-parse "^3.0.0" + universal-user-agent "^7.0.2" + "@octokit/request@^8.4.1": version "8.4.1" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.4.1.tgz#715a015ccf993087977ea4365c44791fc4572486" @@ -1760,6 +1883,16 @@ "@octokit/plugin-request-log" "^4.0.0" "@octokit/plugin-rest-endpoint-methods" "13.3.2-cjs.1" +"@octokit/rest@^22.0.0": + version "22.0.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-22.0.0.tgz#9026f47dacba9c605da3d43cce9432c4c532dc5a" + integrity sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA== + dependencies: + "@octokit/core" "^7.0.2" + "@octokit/plugin-paginate-rest" "^13.0.1" + "@octokit/plugin-request-log" "^6.0.0" + "@octokit/plugin-rest-endpoint-methods" "^16.0.0" + "@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.10.0", "@octokit/types@^13.7.0", "@octokit/types@^13.8.0": version "13.10.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.10.0.tgz#3e7c6b19c0236c270656e4ea666148c2b51fd1a3" @@ -1774,6 +1907,13 @@ dependencies: "@octokit/openapi-types" "^25.0.0" +"@octokit/types@^14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-14.1.0.tgz#3bf9b3a3e3b5270964a57cc9d98592ed44f840f2" + integrity sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g== + dependencies: + "@octokit/openapi-types" "^25.1.0" + "@react-native-community/cli-clean@20.0.0": version "20.0.0" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-20.0.0.tgz#e685f5404195ded69c81d1394e8c5eb332b780bc" @@ -3007,6 +3147,11 @@ before-after-hook@^3.0.2: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d" integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A== +before-after-hook@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-4.0.0.tgz#cf1447ab9160df6a40f3621da64d6ffc36050cb9" + integrity sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ== + bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -4433,6 +4578,11 @@ fast-content-type-parse@^2.0.0: resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz#c236124534ee2cb427c8d8e5ba35a4856947847b" integrity sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q== +fast-content-type-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz#5590b6c807cc598be125e6740a9fde589d2b7afb" + integrity sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -6061,6 +6211,11 @@ jsonc-eslint-parser@^2.3.0: espree "^9.0.0" semver "^7.3.5" +jsonc-parser@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== + jsonc-parser@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" From d7315563fee6e556fe76d2221ae217d0f8ed0edf Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 4 Sep 2025 16:27:46 -0700 Subject: [PATCH 0058/1110] Allow ReactInstance to evaluate SH unit when possible (#53471) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53471 When IHermes::getSHUnitCreator() returns non-null pointer, call `evaluateSHUnit()` on that instead of `evaluateJavaScript()`. Changelog: [Internal] Reviewed By: dannysu Differential Revision: D80916868 fbshipit-source-id: 9c1e2327b720cab4b374d4752a9c64f87c592ad6 --- .../ReactCommon/react/runtime/ReactInstance.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index ea24e5c8682e..e0d9779d6b2d 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -242,7 +243,17 @@ void ReactInstance::loadScript( ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str()); } - runtime.evaluateJavaScript(buffer, sourceURL); + // Check if the shermes unit is avaliable. + auto* shUnitAPI = jsi::castInterface(&runtime); + auto* shUnitCreator = shUnitAPI ? shUnitAPI->getSHUnitCreator() : nullptr; + if (shUnitCreator) { + LOG(WARNING) << "ReactInstance: evaluateSHUnit"; + auto* hermesAPI = jsi::castInterface(&runtime); + hermesAPI->evaluateSHUnit(shUnitCreator); + } else { + LOG(WARNING) << "ReactInstance: evaluateJavaScript() with JS bundle"; + runtime.evaluateJavaScript(buffer, sourceURL); + } /** * TODO(T183610671): We need a safe/reliable way to enable the js From 020c92efac431ee4728586c94c959daf0be843d4 Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Thu, 4 Sep 2025 16:51:26 -0700 Subject: [PATCH 0059/1110] Deploy 0.281.0 to xplat (#53607) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53607 [changelog](https://github.com/facebook/flow/blob/main/Changelog.md) Changelog: [Internal] Reviewed By: gkz Differential Revision: D81728906 fbshipit-source-id: 161eb62d7520398c97f05db40676e1ea2ac4d0a9 --- .flowconfig | 7 +------ package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.flowconfig b/.flowconfig index 6d4cec2f2326..793fae5bce38 100644 --- a/.flowconfig +++ b/.flowconfig @@ -75,11 +75,6 @@ module.system.haste.module_ref_prefix=m# react.runtime=automatic -experimental.only_support_flow_fixme_and_expected_error=true -experimental.require_suppression_with_error_code=true -experimental.invariant_subtyping_error_message_improvement=true -experimental.natural_inference.local_object_literals.followup_fix=true - ban_spread_key_props=true [lints] @@ -103,4 +98,4 @@ untyped-import untyped-type-import [version] -^0.280.0 +^0.281.0 diff --git a/package.json b/package.json index df0ce9eb20c6..60030adf24ff 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "eslint-plugin-relay": "^1.8.3", "fb-dotslash": "0.5.8", "flow-api-translator": "0.32.0", - "flow-bin": "^0.280.0", + "flow-bin": "^0.281.0", "glob": "^7.1.1", "hermes-eslint": "0.32.0", "hermes-transform": "0.32.0", diff --git a/yarn.lock b/yarn.lock index 033115077295..5076fa07d9ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4776,10 +4776,10 @@ flow-api-translator@0.32.0: hermes-transform "0.32.0" typescript "5.3.2" -flow-bin@^0.280.0: - version "0.280.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.280.0.tgz#73db532b4ea072a20a47277a38c0ab9f4f4600e6" - integrity sha512-7WHDjleRd6KDggSYovdrNSz1xMM9HKSI3ajHF3xMmWaETxx3SHnl60cclW6mPm3z+0FUVQSr7XFLiGSW3Zkq7Q== +flow-bin@^0.281.0: + version "0.281.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.281.0.tgz#be49afd6da986ba355e27d38775547b60b398e65" + integrity sha512-jgSKNLolqwtI4CZ/lTh/YKf0JAtFGTrf/8ETZkfxxyT5AYB9NfiO5KQttW0gtd63plppvw3ghyVFKLSK3TH6hg== flow-enums-runtime@^0.0.6: version "0.0.6" From a50ddf3d8e6ee5c6a08de70d6becffa136af98e2 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Thu, 4 Sep 2025 19:12:08 -0700 Subject: [PATCH 0060/1110] JS: Upgrade to `signedsource@2.0.0` (#53606) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53606 Upgrades projects to `signedsource@2.0.0`, which includes a critical bug fix to the `isSigned` and `signFile` functions: ```lang=diff isSigned(data) { - return !PATTERN.exec(data); + return PATTERN.exec(data) != null; }, ``` Changelog: [Internal] Reviewed By: bvanderhoof, jehartzog Differential Revision: D81723007 fbshipit-source-id: 0606eef35df1e5ec988b537aa012bc2c6d3c2d3a --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 60030adf24ff..a9349970d375 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "react-test-renderer": "19.1.1", "rimraf": "^3.0.2", "shelljs": "^0.8.5", - "signedsource": "^1.0.0", + "signedsource": "^2.0.0", "supports-color": "^7.1.0", "temp-dir": "^2.0.0", "tinybench": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 5076fa07d9ee..dc7be140a75f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8482,10 +8482,10 @@ signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -signedsource@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" - integrity sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww== +signedsource@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-2.0.0.tgz#f72dc0f98f5bca2763b464a555511a84a4da8eee" + integrity sha512-MscTxXbMij5JVgrW1xDiMIc+vFa0+H0+HP+rRrFjwa7ef2VAxIP/4L/E75I5H4xvyb4l1X+a9ch+6Zy5uFu7Fg== sisteransi@^1.0.5: version "1.0.5" From 1be852781d9105f509d2f02ff6e08e6117a27d1d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 4 Sep 2025 23:31:08 -0700 Subject: [PATCH 0061/1110] Add tests for isHermesV1Enabled (#53601) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53601 Changelog: [Internal] Adds tests covering the `isHermesV1Enabled` utility function. Reviewed By: cortinico Differential Revision: D81681635 fbshipit-source-id: c2c50db65f93b8b58ce1730ccfdf4367a024ce7b --- .../facebook/react/utils/ProjectUtilsTest.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt index 81ec6744f00b..2e1e3590fe19 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt @@ -14,6 +14,7 @@ import com.facebook.react.tests.createProject import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled import com.facebook.react.utils.ProjectUtils.isHermesEnabled +import com.facebook.react.utils.ProjectUtils.isHermesV1Enabled import com.facebook.react.utils.ProjectUtils.isNewArchEnabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson import java.io.File @@ -115,6 +116,32 @@ class ProjectUtilsTest { assertThat(project.isEdgeToEdgeEnabled).isFalse() } + @Test + fun isHermesV1Enabled_returnsFalseByDefault() { + assertThat(createProject().isHermesV1Enabled).isFalse() + } + + @Test + fun isHermesV1Enabled_withDisabledViaProperty_returnsFalse() { + val project = createProject() + project.extensions.extraProperties.set("hermesV1Enabled", "false") + assertThat(project.isHermesV1Enabled).isFalse() + } + + @Test + fun isHermesV1Enabled_withEnabledViaProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("hermesV1Enabled", "true") + assertThat(project.isHermesV1Enabled).isTrue() + } + + @Test + fun isHermesV1Enabled_withInvalidViaProperty_returnsFalse() { + val project = createProject() + project.extensions.extraProperties.set("hermesV1Enabled", "¯\\_(ツ)_/¯") + assertThat(project.isHermesV1Enabled).isFalse() + } + @Test fun needsCodegenFromPackageJson_withCodegenConfigInPackageJson_returnsTrue() { val project = createProject() From f6a4f24090bca96be5e5e0cb082ca1068aa905ff Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Fri, 5 Sep 2025 03:05:35 -0700 Subject: [PATCH 0062/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages [A] (#53612) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53612 Reviewed By: rshest Differential Revision: D81699159 fbshipit-source-id: b711e066b206d70ec40570b63dd1290d800e531f --- ...TCxxInspectorPackagerConnectionDelegate.mm | 2 +- .../RCTCxxInspectorWebSocketAdapter.mm | 4 +- .../React/Views/RCTComponentData.mm | 38 +++++++++---------- .../jni/first-party/fbgloginit/glog_init.cpp | 2 +- .../devsupport/JInspectorNetworkReporter.cpp | 5 ++- .../ReactCommon/jsc/JSCRuntime.cpp | 24 ++++++------ .../tests/InspectorPackagerConnectionTest.cpp | 4 +- .../tests/JsiIntegrationTest.h | 8 ++-- .../android/ReactCommon/JavaTurboModule.cpp | 3 +- 9 files changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm index b7bf705b2892..c85bd4d3aa4a 100644 --- a/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm +++ b/packages/react-native/React/Inspector/RCTCxxInspectorPackagerConnectionDelegate.mm @@ -32,7 +32,7 @@ std::weak_ptr delegate) { auto *adapter = [[RCTCxxInspectorWebSocketAdapter alloc] initWithURL:url delegate:delegate]; - if (!adapter) { + if (adapter == nullptr) { return nullptr; } return std::make_unique(adapter); diff --git a/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm b/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm index 9ab428fe398b..4b0754b26ae1 100644 --- a/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm +++ b/packages/react-native/React/Inspector/RCTCxxInspectorWebSocketAdapter.mm @@ -34,7 +34,7 @@ @interface RCTCxxInspectorWebSocketAdapter () { @implementation RCTCxxInspectorWebSocketAdapter - (instancetype)initWithURL:(const std::string &)url delegate:(std::weak_ptr)delegate { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _delegate = delegate; _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:NSStringFromUTF8StringView(url)]]; _webSocket.delegate = self; @@ -49,7 +49,7 @@ - (void)send:(std::string_view)message NSString *messageStr = NSStringFromUTF8StringView(message); dispatch_async(dispatch_get_main_queue(), ^{ RCTCxxInspectorWebSocketAdapter *strongSelf = weakSelf; - if (strongSelf) { + if (strongSelf != nullptr) { [strongSelf->_webSocket sendString:messageStr error:NULL]; } }); diff --git a/packages/react-native/React/Views/RCTComponentData.mm b/packages/react-native/React/Views/RCTComponentData.mm index 5e39f77c86c4..36de415c2b4f 100644 --- a/packages/react-native/React/Views/RCTComponentData.mm +++ b/packages/react-native/React/Views/RCTComponentData.mm @@ -50,7 +50,7 @@ - (instancetype)initWithManagerClass:(Class)managerClass bridge:(RCTBridge *)bridge eventDispatcher:(id)eventDispatcher { - if ((self = [super init])) { + if ((self = [super init]) != nullptr) { _bridge = bridge; _eventDispatcher = eventDispatcher; _managerClass = managerClass; @@ -71,12 +71,12 @@ - (BOOL)isBridgeMode - (RCTViewManager *)manager { - if (!_manager && [self isBridgeMode]) { + if ((_manager == nullptr) && [self isBridgeMode]) { _manager = [_bridge moduleForClass:_managerClass]; - } else if (!_manager && !_bridgelessViewManager) { + } else if ((_manager == nullptr) && (_bridgelessViewManager == nullptr)) { _bridgelessViewManager = [_bridge moduleForClass:_managerClass]; } - return _manager ? _manager : _bridgelessViewManager; + return (_manager != nullptr) ? _manager : _bridgelessViewManager; } RCT_NOT_IMPLEMENTED(-(instancetype)init) @@ -106,7 +106,7 @@ - (void)callCustomSetter:(SEL)setter onView:(id)view withProp:(id) { json = RCTNilIfNull(json); if (!isShadowView) { - if (!json && !_defaultView) { + if ((json == nullptr) && (_defaultView == nullptr)) { // Only create default view if json is null _defaultView = [self createViewWithTag:nil rootTag:nil]; } @@ -130,11 +130,11 @@ static RCTPropBlock createEventSetter( eventHandler = ^(NSDictionary *event) { // The component no longer exists, we shouldn't send the event id strongTarget = weakTarget; - if (!strongTarget) { + if (strongTarget == nullptr) { return; } - if (eventInterceptor) { + if (eventInterceptor != nullptr) { eventInterceptor(propName, event, strongTarget.reactTag); } else { RCTComponentEvent *componentEvent = [[RCTComponentEvent alloc] initWithName:propName @@ -158,13 +158,13 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S __block NSMutableData *defaultValue = nil; return ^(id target, id json) { - if (!target) { + if (target == nullptr) { return; } // Get default value - if (!defaultValue) { - if (!json) { + if (defaultValue == nullptr) { + if (json == nullptr) { // We only set the defaultValue when we first pass a non-null // value, so if the first value sent for a prop is null, it's // a no-op (we'd be resetting it to its default when its @@ -186,10 +186,10 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S // Get value BOOL freeValueOnCompletion = NO; void *value = defaultValue.mutableBytes; - if (json) { + if (json != nullptr) { freeValueOnCompletion = YES; value = malloc(typeSignature.methodReturnLength); - if (!value) { + if (value == nullptr) { // CWE - 391 : Unchecked error condition // https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html // https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c @@ -201,7 +201,7 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S } // Set value - if (!targetInvocation) { + if (targetInvocation == nullptr) { NSMethodSignature *signature = [target methodSignatureForSelector:setter]; targetInvocation = [NSInvocation invocationWithMethodSignature:signature]; targetInvocation.selector = setter; @@ -252,7 +252,7 @@ - (RCTPropBlock)createPropBlock:(NSString *)name isShadowView:(BOOL)isShadowView // Disect keypath NSString *key = name; NSArray *parts = [keyPath componentsSeparatedByString:@"."]; - if (parts) { + if (parts != nullptr) { key = parts.lastObject; parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}]; } @@ -275,7 +275,7 @@ - (RCTPropBlock)createPropBlock:(NSString *)name isShadowView:(BOOL)isShadowView } else { // Ordinary property handlers NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type]; - if (!typeSignature) { + if (typeSignature == nullptr) { RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type)); return ^(__unused id view, __unused id json) { }; @@ -347,7 +347,7 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name isShadowView:(BOOL)isShadowView { RCTPropBlockDictionary *propBlocks = isShadowView ? _shadowPropBlocks : _viewPropBlocks; RCTPropBlock propBlock = propBlocks[name]; - if (!propBlock) { + if (propBlock == nullptr) { propBlock = [self createPropBlock:name isShadowView:isShadowView]; #if RCT_DEBUG @@ -381,7 +381,7 @@ - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowV - (void)setProps:(NSDictionary *)props forView:(id)view isShadowView:(BOOL)isShadowView { - if (!view) { + if (view == nullptr) { return; } @@ -467,13 +467,13 @@ - (void)setProps:(NSDictionary *)props forView:(id // We need to handle both propConfig_* and propConfigShadow_* methods const char *underscorePos = strchr(selectorName + strlen("propConfig"), '_'); - if (!underscorePos) { + if (underscorePos == nullptr) { continue; } NSString *name = @(underscorePos + 1); NSString *type = ((NSArray * (*)(id, SEL)) objc_msgSend)(managerClass, selector)[0]; - if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) { + if (RCT_DEBUG && (propTypes[name] != nullptr) && ![propTypes[name] isEqualToString:type]) { RCTLogError( @"Property '%@' of component '%@' redefined from '%@' " "to '%@'", diff --git a/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp b/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp index dac26d8d0c07..c7174b832275 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp @@ -102,7 +102,7 @@ lastResort(const char* tag, const char* msg, const char* arg = nullptr) { } #else std::cerr << msg; - if (arg) { + if (arg != nullptr) { std::cerr << ": " << arg; } std::cerr << std::endl; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp index 41e25e3c85cc..34eda93de81c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp @@ -60,7 +60,8 @@ static std::unordered_map responseBuffers; /* static */ jboolean JInspectorNetworkReporter::isDebuggingEnabled( jni::alias_ref /*unused*/) { - return NetworkReporter::getInstance().isDebuggingEnabled(); + return static_cast( + NetworkReporter::getInstance().isDebuggingEnabled()); } /* static */ void JInspectorNetworkReporter::reportRequestStart( @@ -138,7 +139,7 @@ static std::unordered_map responseBuffers; jint requestId, jboolean cancelled) { NetworkReporter::getInstance().reportRequestFailed( - std::to_string(requestId), cancelled); + std::to_string(requestId), cancelled != 0u); } /* static */ void JInspectorNetworkReporter::maybeStoreResponseBodyImpl( diff --git a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp index 46528bad7450..3d14d45a70b4 100644 --- a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp +++ b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp @@ -320,7 +320,7 @@ std::string JSStringToSTLString(JSStringRef str) { buffer = heapBuffer.get(); } size_t actualBytes = JSStringGetUTF8CString(str, buffer, maxBytes); - if (!actualBytes) { + if (actualBytes == 0u) { // Happens if maxBytes == 0 (never the case here) or if str contains // invalid UTF-16 data, since JSStringGetUTF8CString attempts a strict // conversion. @@ -437,7 +437,7 @@ jsi::Value JSCRuntime::evaluateJavaScript( JSValueRef res = JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc); JSStringRelease(sourceRef); - if (sourceURLRef) { + if (sourceURLRef != nullptr) { JSStringRelease(sourceURLRef); } checkException(res, exc); @@ -597,7 +597,7 @@ void JSCRuntime::JSCObjectValue::invalidate() noexcept { jsi::Runtime::PointerValue* JSCRuntime::cloneSymbol( const jsi::Runtime::PointerValue* pv) { - if (!pv) { + if (pv == nullptr) { return nullptr; } const JSCSymbolValue* symbol = static_cast(pv); @@ -611,7 +611,7 @@ jsi::Runtime::PointerValue* JSCRuntime::cloneBigInt( jsi::Runtime::PointerValue* JSCRuntime::cloneString( const jsi::Runtime::PointerValue* pv) { - if (!pv) { + if (pv == nullptr) { return nullptr; } const JSCStringValue* string = static_cast(pv); @@ -620,7 +620,7 @@ jsi::Runtime::PointerValue* JSCRuntime::cloneString( jsi::Runtime::PointerValue* JSCRuntime::cloneObject( const jsi::Runtime::PointerValue* pv) { - if (!pv) { + if (pv == nullptr) { return nullptr; } const JSCObjectValue* object = static_cast(pv); @@ -632,7 +632,7 @@ jsi::Runtime::PointerValue* JSCRuntime::cloneObject( jsi::Runtime::PointerValue* JSCRuntime::clonePropNameID( const jsi::Runtime::PointerValue* pv) { - if (!pv) { + if (pv == nullptr) { return nullptr; } const JSCStringValue* string = static_cast(pv); @@ -914,7 +914,7 @@ JSClassRef getNativeStateClass() { } // namespace JSValueRef JSCRuntime::getNativeStateSymbol() { - if (!nativeStateSymbol_) { + if (nativeStateSymbol_ == nullptr) { JSStringRef symbolName = JSStringCreateWithUTF8CString("__internal_nativeState"); JSValueRef symbol = JSValueMakeSymbol(ctx_, symbolName); @@ -1182,7 +1182,7 @@ jsi::Function JSCRuntime::createFunctionFromHostFunction( kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete, &exc); - if (exc) { + if (exc != nullptr) { // Silently fail to set length exc = nullptr; } @@ -1198,7 +1198,7 @@ jsi::Function JSCRuntime::createFunctionFromHostFunction( kJSPropertyAttributeDontDelete, &exc); JSStringRelease(name); - if (exc) { + if (exc != nullptr) { // Silently fail to set name exc = nullptr; } @@ -1211,7 +1211,7 @@ jsi::Function JSCRuntime::createFunctionFromHostFunction( abort(); } JSObjectRef funcCtor = JSValueToObject(ctx, value, &exc); - if (!funcCtor) { + if (funcCtor == nullptr) { // We can't do anything if Function is not an object return; } @@ -1439,7 +1439,7 @@ JSStringRef getEmptyString() { jsi::Runtime::PointerValue* JSCRuntime::makeStringValue( JSStringRef stringRef) const { - if (!stringRef) { + if (stringRef == nullptr) { stringRef = getEmptyString(); } #ifndef NDEBUG @@ -1463,7 +1463,7 @@ jsi::PropNameID JSCRuntime::createPropNameID(JSStringRef str) { jsi::Runtime::PointerValue* JSCRuntime::makeObjectValue( JSObjectRef objectRef) const { - if (!objectRef) { + if (objectRef == nullptr) { objectRef = JSObjectMake(ctx_, nullptr, nullptr); } #ifndef NDEBUG diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp index 5b237b73ee90..f61eca78251c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/InspectorPackagerConnectionTest.cpp @@ -59,7 +59,7 @@ class InspectorPackagerConnectionTestBase : public testing::Test { auto pages = getInspectorInstance().getPages(); int liveConnectionCount = 0; for (size_t i = 0; i != localConnections_.objectsVended(); ++i) { - if (localConnections_[i]) { + if (localConnections_[i] != nullptr) { liveConnectionCount++; // localConnections_[i] is a strict mock and will complain when we // removePage if the call is unexpected. @@ -69,7 +69,7 @@ class InspectorPackagerConnectionTestBase : public testing::Test { for (auto& page : pages) { getInspectorInstance().removePage(page.id); } - if (!pages.empty() && liveConnectionCount) { + if (!pages.empty() && (liveConnectionCount != 0)) { if (!::testing::Test::HasFailure()) { FAIL() << "Test case ended with " << liveConnectionCount diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h index c8cd92494188..9c53181f50ea 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h @@ -65,12 +65,12 @@ class JsiIntegrationPortableTestBase : public ::testing::Test, ~JsiIntegrationPortableTestBase() override { toPage_.reset(); - if (runtimeTarget_) { + if (runtimeTarget_ != nullptr) { EXPECT_TRUE(instance_); instance_->unregisterRuntime(*runtimeTarget_); runtimeTarget_ = nullptr; } - if (instance_) { + if (instance_ != nullptr) { page_->unregisterInstance(*instance_); instance_ = nullptr; } @@ -108,12 +108,12 @@ class JsiIntegrationPortableTestBase : public ::testing::Test, } void reload() { - if (runtimeTarget_) { + if (runtimeTarget_ != nullptr) { ASSERT_TRUE(instance_); instance_->unregisterRuntime(*runtimeTarget_); runtimeTarget_ = nullptr; } - if (instance_) { + if (instance_ != nullptr) { page_->unregisterInstance(*instance_); instance_ = nullptr; } diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index 1b1c6f7f7e7c..5c9b464a8c3a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -395,7 +395,8 @@ JNIArgs convertJSIArgsToJNIArgs( "boolean", argIndex, methodName, arg, &rt); } jarg->l = makeGlobalIfNecessary( - jni::JBoolean::valueOf(arg->getBool()).release()); + jni::JBoolean::valueOf(static_cast(arg->getBool())) + .release()); } else if (type == "Ljava/lang/String;") { if (!arg->isString()) { throw JavaTurboModuleArgumentConversionException( From 71edbe1548909c29b214f455e52417fdee7eee01 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Fri, 5 Sep 2025 06:01:47 -0700 Subject: [PATCH 0063/1110] Make feature flag "enableViewRecyclingForScrollView" true by default Summary: ## Changelog: [Internal] - Noticed that the default value for this one is inconsistent with all the other similar ones, which can cause confusion during setting up the experiment, fixing it. Note that top level view recycling is still controlled via `enableViewRecycling`, which will also disable all the other ones when false (which it is by default). bypass-github-export-checks Reviewed By: lenaic Differential Revision: D81766029 fbshipit-source-id: df4a260b9bde20d1c85b7786df00fa91298a27b7 --- .../internal/featureflags/ReactNativeFeatureFlagsDefaults.kt | 4 ++-- .../react/featureflags/ReactNativeFeatureFlagsDefaults.h | 4 ++-- .../scripts/featureflags/ReactNativeFeatureFlags.config.js | 2 +- .../src/private/featureflags/ReactNativeFeatureFlags.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 54e65b67538e..8319e3bea365 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<411732d441bacbef37c3474b9041202c>> + * @generated SignedSource<> */ /** @@ -101,7 +101,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableViewRecyclingForImage(): Boolean = true - override fun enableViewRecyclingForScrollView(): Boolean = false + override fun enableViewRecyclingForScrollView(): Boolean = true override fun enableViewRecyclingForText(): Boolean = true diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2969a1fc9711..2e489dc1b537 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6d8adaa4b960407540afb1d6e42a3840>> + * @generated SignedSource<<203fbd20e17d71ad8aed3e4f9a8a9bae>> */ /** @@ -184,7 +184,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { } bool enableViewRecyclingForScrollView() override { - return false; + return true; } bool enableViewRecyclingForText() override { diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 7ec2687d929b..115e40f7dc2c 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -463,7 +463,7 @@ const definitions: FeatureFlagDefinitions = { ossReleaseStage: 'none', }, enableViewRecyclingForScrollView: { - defaultValue: false, + defaultValue: true, metadata: { dateAdded: '2025-08-20', description: diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 3ff0dd58d6f3..dd2e736e6384 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2bef8486b596a7593bbc931da63b2682>> + * @generated SignedSource<<043c615bb3aeec8730273826786b336a>> * @flow strict * @noformat */ @@ -359,7 +359,7 @@ export const enableViewRecyclingForImage: Getter = createNativeFlagGett /** * Enables View Recycling for via ReactViewGroup/ReactViewManager. */ -export const enableViewRecyclingForScrollView: Getter = createNativeFlagGetter('enableViewRecyclingForScrollView', false); +export const enableViewRecyclingForScrollView: Getter = createNativeFlagGetter('enableViewRecyclingForScrollView', true); /** * Enables View Recycling for via ReactTextView/ReactTextViewManager. */ From 3a3f3a417d2da1ca9c4be4ec4499e5d3a2c2babf Mon Sep 17 00:00:00 2001 From: generatedunixname89002005287564 Date: Fri, 5 Sep 2025 06:37:04 -0700 Subject: [PATCH 0064/1110] Fix CQS signal readability-implicit-bool-conversion in xplat/js/react-native-github/packages [B] (#53611) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53611 Reviewed By: rshest Differential Revision: D81699709 fbshipit-source-id: 6f512fcc01e3f1d33b4e94c4766ee3ab759463e5 --- .../ios/ReactCommon/RCTInteropTurboModule.mm | 10 ++-- .../ios/ReactCommon/RCTTurboModule.mm | 30 +++++------ .../ios/ReactCommon/RCTSampleLegacyModule.mm | 8 +-- .../ios/ReactCommon/RCTSampleTurboModule.mm | 8 +-- .../renderer/attributedstring/conversions.h | 50 +++++++++---------- ...cyViewManagerInteropComponentDescriptor.mm | 4 +- .../AndroidTextInputComponentDescriptor.h | 2 +- .../renderer/graphics/HostPlatformColor.mm | 6 +-- .../renderer/graphics/PlatformColorParser.mm | 8 +-- 9 files changed, 63 insertions(+), 63 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm index 3caa14ba6a3a..bdf4c397ef59 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm @@ -51,7 +51,7 @@ std::vector methodInfos; Class cls = moduleClass; - while (cls && cls != [NSObject class] && cls != [NSProxy class]) { + while ((cls != nullptr) && cls != [NSObject class] && cls != [NSProxy class]) { unsigned int methodCount; Method *methods = class_copyMethodList(object_getClass(cls), &methodCount); @@ -482,7 +482,7 @@ T RCTConvertTo(SEL selector, id json) return; } - if (arg) { + if (arg != nullptr) { [retainedObjectsForInvocation addObject:arg]; } [inv setArgument:&arg atIndex:(index) + 2]; @@ -496,7 +496,7 @@ T RCTConvertTo(SEL selector, id json) typeInvocation.target = [RCTConvert class]; void *returnValue = malloc(typeSignature.methodReturnLength); - if (!returnValue) { + if (returnValue == nullptr) { // CWE - 391 : Unchecked error condition // https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html // https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c @@ -519,7 +519,7 @@ T RCTConvertTo(SEL selector, id json) * RCTModuleMethod doesn't actually call into RCTConvert in this case. */ id arg = [objCArg copy]; - if (arg) { + if (arg != nullptr) { [retainedObjectsForInvocation addObject:arg]; } [inv setArgument:&arg atIndex:(index) + 2]; @@ -537,7 +537,7 @@ T RCTConvertTo(SEL selector, id json) RCTResponseSenderBlock arg = (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); - if (arg) { + if (arg != nullptr) { [retainedObjectsForInvocation addObject:arg]; } [inv setArgument:&arg atIndex:(index) + 2]; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index cb70f355df76..664657021cc0 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -59,7 +59,7 @@ static int32_t getUniqueId() static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value) { - return jsi::String::createFromUtf8(runtime, [value UTF8String] ? [value UTF8String] : ""); + return jsi::String::createFromUtf8(runtime, ([value UTF8String] != nullptr) ? [value UTF8String] : ""); } static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value) @@ -124,7 +124,7 @@ static int32_t getUniqueId() for (size_t i = 0; i < size; i++) { // Insert kCFNull when it's `undefined` value to preserve the indices. id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull); - [result addObject:convertedObject ? convertedObject : (id)kCFNull]; + [result addObject:(convertedObject != nullptr) ? convertedObject : (id)kCFNull]; } return result; } @@ -142,7 +142,7 @@ static int32_t getUniqueId() jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime); NSString *k = convertJSIStringToNSString(runtime, name); id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull); - if (v) { + if (v != nullptr) { result[k] = v; } } @@ -252,7 +252,7 @@ id convertJSIValueToObjCObject( jsi::Value ObjCTurboModule::createPromise(jsi::Runtime &runtime, const std::string &methodName, PromiseInvocationBlock invoke) { - if (!invoke) { + if (invoke == nullptr) { return jsi::Value::undefined(); } @@ -388,7 +388,7 @@ id convertJSIValueToObjCObject( void (^block)() = ^{ id strongModule = weakModule; - if (!strongModule) { + if (strongModule == nullptr) { return; } @@ -457,7 +457,7 @@ TraceSection s( void (^block)() = ^{ id strongModule = weakModule; - if (!strongModule) { + if (strongModule == nullptr) { return; } @@ -560,14 +560,14 @@ TraceSection s( */ NSString *ObjCTurboModule::getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex) { - if (!methodArgumentTypeNames_) { + if (methodArgumentTypeNames_ == nullptr) { NSMutableDictionary *> *methodArgumentTypeNames = [NSMutableDictionary new]; unsigned int numberOfMethods; Class cls = [instance_ class]; Method *methods = class_copyMethodList(object_getClass(cls), &numberOfMethods); - if (methods) { + if (methods != nullptr) { for (unsigned int i = 0; i < numberOfMethods; i++) { SEL s = method_getName(methods[i]); NSString *mName = NSStringFromSelector(s); @@ -597,7 +597,7 @@ TraceSection s( methodArgumentTypeNames_ = methodArgumentTypeNames; } - if (methodArgumentTypeNames_[methodName]) { + if (methodArgumentTypeNames_[methodName] != nullptr) { assert([methodArgumentTypeNames_[methodName] count] > argIndex); return methodArgumentTypeNames_[methodName][argIndex]; } @@ -656,7 +656,7 @@ TraceSection s( */ BOOL enableModuleArgumentNSNullConversionIOS = ReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS(); id objCArg = convertJSIValueToObjCObject(runtime, arg, jsInvoker_, enableModuleArgumentNSNullConversionIOS); - if (objCArg) { + if (objCArg != nullptr) { NSString *methodNameNSString = @(methodName); /** @@ -678,7 +678,7 @@ TraceSection s( } [inv setArgument:(void *)&convertedObjCArg atIndex:i + 2]; - if (convertedObjCArg) { + if (convertedObjCArg != nullptr) { [retainedObjectsForInvocation addObject:convertedObjCArg]; } return; @@ -708,7 +708,7 @@ TraceSection s( * Insert converted args unmodified. */ [inv setArgument:(void *)&objCArg atIndex:i + 2]; - if (objCArg) { + if (objCArg != nullptr) { [retainedObjectsForInvocation addObject:objCArg]; } } @@ -848,7 +848,7 @@ TraceSection s( BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, size_t argIndex) { - return methodArgConversionSelectors_ && methodArgConversionSelectors_[methodName] && + return (methodArgConversionSelectors_ != nullptr) && (methodArgConversionSelectors_[methodName] != nullptr) && ![methodArgConversionSelectors_[methodName][argIndex] isEqual:(id)kCFNull]; } @@ -860,11 +860,11 @@ TraceSection s( void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, size_t argIndex, NSString *fnName) { - if (!methodArgConversionSelectors_) { + if (methodArgConversionSelectors_ == nullptr) { methodArgConversionSelectors_ = [NSMutableDictionary new]; } - if (!methodArgConversionSelectors_[methodName]) { + if (methodArgConversionSelectors_[methodName] == nullptr) { auto metaData = methodMap_.at([methodName UTF8String]); auto argCount = metaData.argCount; diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm index 887aed735dcf..8c623a8b5c44 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleLegacyModule.mm @@ -136,14 +136,14 @@ - (NSDictionary *)constantsToExport { return @{ @"x" : @(x), - @"y" : y ? y : [NSNull null], - @"z" : z ? z : [NSNull null], + @"y" : (y != nullptr) ? y : [NSNull null], + @"z" : (z != nullptr) ? z : [NSNull null], }; } RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback) { - if (!callback) { + if (callback == nullptr) { return; } callback(@[ @"value from callback!" ]); @@ -154,7 +154,7 @@ - (NSDictionary *)constantsToExport : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { - if (!resolve || !reject) { + if ((resolve == nullptr) || (reject == nullptr)) { return; } diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm index e19ec0485a5b..9da48632091b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm @@ -136,14 +136,14 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime { return @{ @"x" : @(x), - @"y" : y ? y : [NSNull null], - @"z" : z ? z : [NSNull null], + @"y" : (y != nullptr) ? y : [NSNull null], + @"z" : (z != nullptr) ? z : [NSNull null], }; } RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback) { - if (!callback) { + if (callback == nullptr) { return; } callback(@[ @"value from callback!" ]); @@ -154,7 +154,7 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { - if (!resolve || !reject) { + if ((resolve == nullptr) || (reject == nullptr)) { return; } diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index e1a79cc0ea71..0923e85b2c32 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -402,79 +402,79 @@ inline void fromRawValue( inline std::string toString(const FontVariant& fontVariant) { auto result = std::string{}; auto separator = std::string{", "}; - if ((int)fontVariant & (int)FontVariant::SmallCaps) { + if (((int)fontVariant & (int)FontVariant::SmallCaps) != 0) { result += "small-caps" + separator; } - if ((int)fontVariant & (int)FontVariant::OldstyleNums) { + if (((int)fontVariant & (int)FontVariant::OldstyleNums) != 0) { result += "oldstyle-nums" + separator; } - if ((int)fontVariant & (int)FontVariant::LiningNums) { + if (((int)fontVariant & (int)FontVariant::LiningNums) != 0) { result += "lining-nums" + separator; } - if ((int)fontVariant & (int)FontVariant::TabularNums) { + if (((int)fontVariant & (int)FontVariant::TabularNums) != 0) { result += "tabular-nums" + separator; } - if ((int)fontVariant & (int)FontVariant::ProportionalNums) { + if (((int)fontVariant & (int)FontVariant::ProportionalNums) != 0) { result += "proportional-nums" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticOne) { + if (((int)fontVariant & (int)FontVariant::StylisticOne) != 0) { result += "stylistic-one" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticTwo) { + if (((int)fontVariant & (int)FontVariant::StylisticTwo) != 0) { result += "stylistic-two" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticThree) { + if (((int)fontVariant & (int)FontVariant::StylisticThree) != 0) { result += "stylistic-three" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticFour) { + if (((int)fontVariant & (int)FontVariant::StylisticFour) != 0) { result += "stylistic-four" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticFive) { + if (((int)fontVariant & (int)FontVariant::StylisticFive) != 0) { result += "stylistic-five" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticSix) { + if (((int)fontVariant & (int)FontVariant::StylisticSix) != 0) { result += "stylistic-six" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticSeven) { + if (((int)fontVariant & (int)FontVariant::StylisticSeven) != 0) { result += "stylistic-seven" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticEight) { + if (((int)fontVariant & (int)FontVariant::StylisticEight) != 0) { result += "stylistic-eight" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticNine) { + if (((int)fontVariant & (int)FontVariant::StylisticNine) != 0) { result += "stylistic-nine" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticTen) { + if (((int)fontVariant & (int)FontVariant::StylisticTen) != 0) { result += "stylistic-ten" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticEleven) { + if (((int)fontVariant & (int)FontVariant::StylisticEleven) != 0) { result += "stylistic-eleven" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticTwelve) { + if (((int)fontVariant & (int)FontVariant::StylisticTwelve) != 0) { result += "stylistic-twelve" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticThirteen) { + if (((int)fontVariant & (int)FontVariant::StylisticThirteen) != 0) { result += "stylistic-thirteen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticFourteen) { + if (((int)fontVariant & (int)FontVariant::StylisticFourteen) != 0) { result += "stylistic-fourteen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticFifteen) { + if (((int)fontVariant & (int)FontVariant::StylisticFifteen) != 0) { result += "stylistic-fifteen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticSixteen) { + if (((int)fontVariant & (int)FontVariant::StylisticSixteen) != 0) { result += "stylistic-sixteen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticSeventeen) { + if (((int)fontVariant & (int)FontVariant::StylisticSeventeen) != 0) { result += "stylistic-seventeen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticEighteen) { + if (((int)fontVariant & (int)FontVariant::StylisticEighteen) != 0) { result += "stylistic-eighteen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticNineteen) { + if (((int)fontVariant & (int)FontVariant::StylisticNineteen) != 0) { result += "stylistic-nineteen" + separator; } - if ((int)fontVariant & (int)FontVariant::StylisticTwenty) { + if (((int)fontVariant & (int)FontVariant::StylisticTwenty) != 0) { result += "stylistic-twenty" + separator; } diff --git a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm index 504ed94f61ae..83bcdf79cbb6 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/LegacyViewManagerInteropComponentDescriptor.mm @@ -63,13 +63,13 @@ static Class getViewManagerFromComponentName(const std::string &componentName) // 1. Try to get the manager with the RCT prefix. auto rctViewManagerName = "RCT" + viewManagerName; Class viewManagerClass = NSClassFromString(RCTNSStringFromString(rctViewManagerName)); - if (viewManagerClass) { + if (viewManagerClass != nullptr) { return viewManagerClass; } // 2. Try to get the manager without the prefix. viewManagerClass = NSClassFromString(RCTNSStringFromString(viewManagerName)); - if (viewManagerClass) { + if (viewManagerClass != nullptr) { return viewManagerClass; } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h index ed9dbd220368..bdb6e7a99a03 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h @@ -54,7 +54,7 @@ class AndroidTextInputComponentDescriptor final ->getMethod("getThemeData"); if (getThemeData( - fabricUIManager, surfaceId, defaultTextInputPaddingArray)) { + fabricUIManager, surfaceId, defaultTextInputPaddingArray) != 0u) { jfloat* defaultTextInputPadding = env->GetFloatArrayElements(defaultTextInputPaddingArray, nullptr); theme.start = defaultTextInputPadding[0]; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm index 5ff4b138dc3e..a92380ce282e 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm @@ -29,7 +29,7 @@ bool UIColorIsP3ColorSpace(const std::shared_ptr &uiColor) if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB) { CFStringRef name = CGColorSpaceGetName(colorSpace); - if (name != NULL && CFEqual(name, kCGColorSpaceDisplayP3)) { + if (name != NULL && (CFEqual(name, kCGColorSpaceDisplayP3) != 0u)) { return true; } } @@ -105,7 +105,7 @@ uint32_t ColorFromUIColorForSpecificTraitCollection( UITraitCollection *traitCollection) { UIColor *color = (UIColor *)unwrapManagedObject(uiColor); - if (color) { + if (color != nullptr) { color = [color resolvedColorWithTraitCollection:traitCollection]; return ColorFromUIColor(color); } @@ -199,7 +199,7 @@ uint32_t ColorFromUIColor(const std::shared_ptr &uiColor) Color::Color(std::shared_ptr uiColor) { UIColor *color = ((UIColor *)unwrapManagedObject(uiColor)); - if (color) { + if (color != nullptr) { auto colorHash = hashFromUIColor(uiColor); uiColorHashValue_ = colorHash; } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm index 2cda6ea1e72a..8ba3ec59329a 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm @@ -29,16 +29,16 @@ SharedColor darkSharedColor{}; SharedColor highContrastLightSharedColor{}; SharedColor highContrastDarkSharedColor{}; - if (dynamicItems.count("light")) { + if (dynamicItems.count("light") != 0u) { fromRawValue(contextContainer, surfaceId, dynamicItems.at("light"), lightSharedColor); } - if (dynamicItems.count("dark")) { + if (dynamicItems.count("dark") != 0u) { fromRawValue(contextContainer, surfaceId, dynamicItems.at("dark"), darkSharedColor); } - if (dynamicItems.count("highContrastLight")) { + if (dynamicItems.count("highContrastLight") != 0u) { fromRawValue(contextContainer, surfaceId, dynamicItems.at("highContrastLight"), highContrastLightSharedColor); } - if (dynamicItems.count("highContrastDark")) { + if (dynamicItems.count("highContrastDark") != 0u) { fromRawValue(contextContainer, surfaceId, dynamicItems.at("highContrastDark"), highContrastDarkSharedColor); } From 5774bd105d8f9377fb7f53a260c158fd2fd02271 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Fri, 5 Sep 2025 09:18:50 -0700 Subject: [PATCH 0065/1110] create FeatureFlag overrideBySynchronousMountPropsAtMountingAndroid (#53603) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53603 ## Changelog: [General] [Added] - create FeatureFlag overrideBySynchronousMountPropsAtMountingAndroid Reviewed By: rshest Differential Revision: D81690079 fbshipit-source-id: cb381004135ef9cd072c6f99703d9e7f4a40dd6a --- .../featureflags/ReactNativeFeatureFlags.kt | 8 ++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 ++- .../ReactNativeFeatureFlagsAccessor.cpp | 60 ++++++++++++------- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +++- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 ++- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 11 ++++ .../featureflags/ReactNativeFeatureFlags.js | 7 ++- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 20 files changed, 157 insertions(+), 40 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 2e8ddc6b2978..568f8b059e01 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<92a6edded037a504fc1c2c8ae88deae1>> + * @generated SignedSource<> */ /** @@ -324,6 +324,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun hideOffscreenVirtualViewsOnIOS(): Boolean = accessor.hideOffscreenVirtualViewsOnIOS() + /** + * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. + */ + @JvmStatic + public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean = accessor.overrideBySynchronousMountPropsAtMountingAndroid() + /** * Enable the V2 in-app Performance Monitor. This flag is global and should not be changed across React Host lifetimes. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 0b66c832b802..b426a1fd658d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<4ed350d8dfa42caf27d346dfcfbed974>> */ /** @@ -69,6 +69,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxEnabledReleaseCache: Boolean? = null private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null + private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfMonitorV2EnabledCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null private var preventShadowTreeCommitExhaustionCache: Boolean? = null @@ -531,6 +532,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean { + var cached = overrideBySynchronousMountPropsAtMountingAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.overrideBySynchronousMountPropsAtMountingAndroid() + overrideBySynchronousMountPropsAtMountingAndroidCache = cached + } + return cached + } + override fun perfMonitorV2Enabled(): Boolean { var cached = perfMonitorV2EnabledCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 615bdf546b43..df9b8934a0ab 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4410628511f112f3eef22434a05fa757>> + * @generated SignedSource<<6c201de02071a834e411b6761d7f863b>> */ /** @@ -126,6 +126,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun hideOffscreenVirtualViewsOnIOS(): Boolean + @DoNotStrip @JvmStatic public external fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun perfMonitorV2Enabled(): Boolean @DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 8319e3bea365..122dea6b3c5f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<88256ff0f51c33caee5af50bb3424288>> */ /** @@ -121,6 +121,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun hideOffscreenVirtualViewsOnIOS(): Boolean = false + override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean = false + override fun perfMonitorV2Enabled(): Boolean = false override fun preparedTextCacheSize(): Double = 200.0 diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index d9235b1552a4..ba9bd8491674 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2f65e0e9066a8c2c35c44ea7c73dbedc>> + * @generated SignedSource<<57fca2370d6b3f1edf7045a0b4c94350>> */ /** @@ -73,6 +73,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxEnabledReleaseCache: Boolean? = null private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null + private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfMonitorV2EnabledCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null private var preventShadowTreeCommitExhaustionCache: Boolean? = null @@ -584,6 +585,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean { + var cached = overrideBySynchronousMountPropsAtMountingAndroidCache + if (cached == null) { + cached = currentProvider.overrideBySynchronousMountPropsAtMountingAndroid() + accessedFeatureFlags.add("overrideBySynchronousMountPropsAtMountingAndroid") + overrideBySynchronousMountPropsAtMountingAndroidCache = cached + } + return cached + } + override fun perfMonitorV2Enabled(): Boolean { var cached = perfMonitorV2EnabledCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 7f661006d780..6dcce666f1f4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -121,6 +121,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun hideOffscreenVirtualViewsOnIOS(): Boolean + @DoNotStrip public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean + @DoNotStrip public fun perfMonitorV2Enabled(): Boolean @DoNotStrip public fun preparedTextCacheSize(): Double diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index fd322ea6e133..2a85b8f62b5d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<846db2d7c3c6020dec04789fe2784f8a>> */ /** @@ -333,6 +333,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool overrideBySynchronousMountPropsAtMountingAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("overrideBySynchronousMountPropsAtMountingAndroid"); + return method(javaProvider_); + } + bool perfMonitorV2Enabled() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("perfMonitorV2Enabled"); @@ -702,6 +708,11 @@ bool JReactNativeFeatureFlagsCxxInterop::hideOffscreenVirtualViewsOnIOS( return ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS(); } +bool JReactNativeFeatureFlagsCxxInterop::overrideBySynchronousMountPropsAtMountingAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::perfMonitorV2Enabled( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::perfMonitorV2Enabled(); @@ -980,6 +991,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "hideOffscreenVirtualViewsOnIOS", JReactNativeFeatureFlagsCxxInterop::hideOffscreenVirtualViewsOnIOS), + makeNativeMethod( + "overrideBySynchronousMountPropsAtMountingAndroid", + JReactNativeFeatureFlagsCxxInterop::overrideBySynchronousMountPropsAtMountingAndroid), makeNativeMethod( "perfMonitorV2Enabled", JReactNativeFeatureFlagsCxxInterop::perfMonitorV2Enabled), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index ace59ba39374..78bd9446464a 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6a98f8398948f8d56e51d3eff19c5d05>> + * @generated SignedSource<> */ /** @@ -177,6 +177,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool hideOffscreenVirtualViewsOnIOS( facebook::jni::alias_ref); + static bool overrideBySynchronousMountPropsAtMountingAndroid( + facebook::jni::alias_ref); + static bool perfMonitorV2Enabled( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 83c4a3c4136c..5bde2a82dd93 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5bd932f1d52596dad6ea86a2674170ff>> + * @generated SignedSource<> */ /** @@ -222,6 +222,10 @@ bool ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS() { return getAccessor().hideOffscreenVirtualViewsOnIOS(); } +bool ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid() { + return getAccessor().overrideBySynchronousMountPropsAtMountingAndroid(); +} + bool ReactNativeFeatureFlags::perfMonitorV2Enabled() { return getAccessor().perfMonitorV2Enabled(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index f6a20cb6d7ec..0f69f8e64089 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3cde1e9bcf234515551ba57ecf02e3a7>> + * @generated SignedSource<<8979be03db13b9f45d313018192c0c35>> */ /** @@ -284,6 +284,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool hideOffscreenVirtualViewsOnIOS(); + /** + * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. + */ + RN_EXPORT static bool overrideBySynchronousMountPropsAtMountingAndroid(); + /** * Enable the V2 in-app Performance Monitor. This flag is global and should not be changed across React Host lifetimes. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 291aee404016..76e56848e1f5 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -911,6 +911,24 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingAndroid() { + auto flagValue = overrideBySynchronousMountPropsAtMountingAndroid_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(49, "overrideBySynchronousMountPropsAtMountingAndroid"); + + flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); + overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { auto flagValue = perfMonitorV2Enabled_.load(); @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "perfMonitorV2Enabled"); + markFlagAsAccessed(50, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -938,7 +956,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "preparedTextCacheSize"); + markFlagAsAccessed(51, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(52, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(53, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(54, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(55, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(56, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(57, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(58, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useFabricInterop"); + markFlagAsAccessed(59, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(60, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(61, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(62, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(63, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "useRawPropsJsiValue"); + markFlagAsAccessed(64, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "useShadowNodeStateOnClone"); + markFlagAsAccessed(65, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "useTurboModuleInterop"); + markFlagAsAccessed(66, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "useTurboModules"); + markFlagAsAccessed(67, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1244,7 +1262,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "virtualViewHysteresisRatio"); + markFlagAsAccessed(68, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1262,7 +1280,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "virtualViewPrerenderRatio"); + markFlagAsAccessed(69, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index a580922d1ec7..92dbd53cb37a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -81,6 +81,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxEnabledRelease(); bool fuseboxNetworkInspectionEnabled(); bool hideOffscreenVirtualViewsOnIOS(); + bool overrideBySynchronousMountPropsAtMountingAndroid(); bool perfMonitorV2Enabled(); double preparedTextCacheSize(); bool preventShadowTreeCommitExhaustion(); @@ -112,7 +113,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 69> accessedFeatureFlags_; + std::array, 70> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -163,6 +164,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxEnabledRelease_; std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> hideOffscreenVirtualViewsOnIOS_; + std::atomic> overrideBySynchronousMountPropsAtMountingAndroid_; std::atomic> perfMonitorV2Enabled_; std::atomic> preparedTextCacheSize_; std::atomic> preventShadowTreeCommitExhaustion_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2e489dc1b537..f1f08c65ee76 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<203fbd20e17d71ad8aed3e4f9a8a9bae>> + * @generated SignedSource<> */ /** @@ -223,6 +223,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool overrideBySynchronousMountPropsAtMountingAndroid() override { + return false; + } + bool perfMonitorV2Enabled() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 18c52e44c7e5..02c7a2d48e86 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<742e5536d6d174e42a71b205789f93fd>> */ /** @@ -486,6 +486,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::hideOffscreenVirtualViewsOnIOS(); } + bool overrideBySynchronousMountPropsAtMountingAndroid() override { + auto value = values_["overrideBySynchronousMountPropsAtMountingAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::overrideBySynchronousMountPropsAtMountingAndroid(); + } + bool perfMonitorV2Enabled() override { auto value = values_["perfMonitorV2Enabled"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 6eb9a1e93b2a..284c5474e7c4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<07b25b19a265e8afe53eeeffade4dfc0>> + * @generated SignedSource<<40a8180c5d1ebf49c72e6f0fea4201c9>> */ /** @@ -74,6 +74,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxEnabledRelease() = 0; virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool hideOffscreenVirtualViewsOnIOS() = 0; + virtual bool overrideBySynchronousMountPropsAtMountingAndroid() = 0; virtual bool perfMonitorV2Enabled() = 0; virtual double preparedTextCacheSize() = 0; virtual bool preventShadowTreeCommitExhaustion() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index bd63a24e7483..6423c16c52d8 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<348005db0ceee698cd7c33f29bcf85a6>> + * @generated SignedSource<<95f3b1a29280420bf97cdadbcf9f11da>> */ /** @@ -289,6 +289,11 @@ bool NativeReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS( return ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS(); } +bool NativeReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(); +} + bool NativeReactNativeFeatureFlags::perfMonitorV2Enabled( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::perfMonitorV2Enabled(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index b3f455bde8be..ad8249bb0834 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -134,6 +134,8 @@ class NativeReactNativeFeatureFlags bool hideOffscreenVirtualViewsOnIOS(jsi::Runtime& runtime); + bool overrideBySynchronousMountPropsAtMountingAndroid(jsi::Runtime& runtime); + bool perfMonitorV2Enabled(jsi::Runtime& runtime); double preparedTextCacheSize(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 115e40f7dc2c..3fdcc220e80e 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -570,6 +570,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + overrideBySynchronousMountPropsAtMountingAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2025-09-04', + description: + 'Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, perfMonitorV2Enabled: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index dd2e736e6384..331b5716ec8a 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<043c615bb3aeec8730273826786b336a>> + * @generated SignedSource<<8b7c4c7178448a1ea3f55b4cdbe3b95e>> * @flow strict * @noformat */ @@ -99,6 +99,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fuseboxEnabledRelease: Getter, fuseboxNetworkInspectionEnabled: Getter, hideOffscreenVirtualViewsOnIOS: Getter, + overrideBySynchronousMountPropsAtMountingAndroid: Getter, perfMonitorV2Enabled: Getter, preparedTextCacheSize: Getter, preventShadowTreeCommitExhaustion: Getter, @@ -396,6 +397,10 @@ export const fuseboxNetworkInspectionEnabled: Getter = createNativeFlag * Hides offscreen VirtualViews on iOS by setting hidden = YES to avoid extra cost of views */ export const hideOffscreenVirtualViewsOnIOS: Getter = createNativeFlagGetter('hideOffscreenVirtualViewsOnIOS', false); +/** + * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. + */ +export const overrideBySynchronousMountPropsAtMountingAndroid: Getter = createNativeFlagGetter('overrideBySynchronousMountPropsAtMountingAndroid', false); /** * Enable the V2 in-app Performance Monitor. This flag is global and should not be changed across React Host lifetimes. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index b874e9013d3b..8d802da16a56 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1c61b3b4390b75dab658518201379081>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -74,6 +74,7 @@ export interface Spec extends TurboModule { +fuseboxEnabledRelease?: () => boolean; +fuseboxNetworkInspectionEnabled?: () => boolean; +hideOffscreenVirtualViewsOnIOS?: () => boolean; + +overrideBySynchronousMountPropsAtMountingAndroid?: () => boolean; +perfMonitorV2Enabled?: () => boolean; +preparedTextCacheSize?: () => number; +preventShadowTreeCommitExhaustion?: () => boolean; From dae2f606c76905de74e76db7b0a20052a5caea46 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Fri, 5 Sep 2025 09:18:50 -0700 Subject: [PATCH 0066/1110] Course correct props at SurfaceMountingManager.updateProps() (#53589) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53589 ## Changelog: [Android] [Changed] - [c++ animated] Course correct props at SurfaceMountingManager.updateProps() Sometimes a React update will try to commit to the same view that native animated modified before via direct manipulation, and after the update host view will use the prop value currently in Fabric. In `AnimatedMountingOverrideDelegate` there's logic to course correct at ShadowTree mount, but if this update is from JS thread, it takes some time to reach mounting layer, at the same time UI thread can still be doing more direct animation updates, and once the corrected change gets there it's already stale. In this diff I added mechanism to keep track of direct manipulation props (or "synchronous mount props" to match the naming of java function `synchronouslyUpdateView...`) and use it to correct what reaches host view. `SurfaceMountingManager.updateProps()` is called by both regular mount and direct manipulation and it's always called on UI thread, so it could be a good candidate to synchronize these 2 scenarios Reviewed By: sammy-SC Differential Revision: D81611823 fbshipit-source-id: 638a59bcd94b3d7e8bab68defd472b2b482dc92f --- .../ReactAndroid/api/ReactAndroid.api | 2 + .../react/fabric/FabricUIManager.java | 3 +- .../react/fabric/mounting/MountingManager.kt | 14 ++- .../mounting/SurfaceMountingManager.java | 116 +++++++++++++++++- .../animated/NativeAnimatedNodesManager.cpp | 20 +-- 5 files changed, 144 insertions(+), 11 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8436cf854074..fb71ef2fee40 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2354,12 +2354,14 @@ public class com/facebook/react/fabric/mounting/SurfaceMountingManager { public fun sendAccessibilityEvent (II)V public fun setJSResponder (IIZ)V public fun stopSurface ()V + public fun storeSynchronousMountPropsOverride (ILcom/facebook/react/bridge/ReadableMap;)V public fun sweepActiveTouchForTag (I)V public fun updateEventEmitter (ILcom/facebook/react/fabric/events/EventEmitterWrapper;)V public fun updateLayout (IIIIIIII)V public fun updateOverflowInset (IIIII)V public fun updatePadding (IIIII)V public fun updateProps (ILcom/facebook/react/bridge/ReadableMap;)V + public fun updatePropsSynchronously (ILcom/facebook/react/bridge/ReadableMap;)V public fun updateState (ILcom/facebook/react/uimanager/StateWrapper;)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index e08055776a3a..f601b0757df8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -791,7 +791,8 @@ public void synchronouslyUpdateViewOnUIThread(final int reactTag, final Readable @Override public void execute(MountingManager mountingManager) { try { - mountingManager.updateProps(reactTag, props); + mountingManager.storeSynchronousMountPropsOverride(reactTag, props); + mountingManager.updatePropsSynchronously(reactTag, props); } catch (Exception ex) { // TODO T42943890: Fix animations in Fabric and remove this try/catch? // There might always be race conditions between surface teardown and diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt index ad8d51324332..be94133ddd5f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt @@ -265,13 +265,23 @@ internal class MountingManager( } @UiThread - fun updateProps(reactTag: Int, props: ReadableMap?) { + fun storeSynchronousMountPropsOverride(reactTag: Int, props: ReadableMap?) { assertOnUiThread() if (props == null) { return } - getSurfaceManagerForViewEnforced(reactTag).updateProps(reactTag, props) + getSurfaceManagerForViewEnforced(reactTag).storeSynchronousMountPropsOverride(reactTag, props) + } + + @UiThread + fun updatePropsSynchronously(reactTag: Int, props: ReadableMap?) { + assertOnUiThread() + if (props == null) { + return + } + + getSurfaceManagerForViewEnforced(reactTag).updatePropsSynchronously(reactTag, props) } /** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java index 705ad0411a03..a1a8a0753c5e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java @@ -27,10 +27,14 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.RetryableMountingLayerException; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeArray; +import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.annotations.UnstableReactNativeAPI; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.common.mapbuffer.MapBuffer; @@ -53,7 +57,10 @@ import com.facebook.react.uimanager.events.EventCategoryDef; import com.facebook.systrace.Systrace; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Queue; @@ -96,6 +103,11 @@ public class SurfaceMountingManager { // This is null *until* StopSurface is called. private SparseArrayCompat mTagSetForStoppedSurface; + // This is to make sure direct manipulation result will not be overridden by React update. + @ThreadConfined(UI) + private final SparseArrayCompat> mTagToSynchronousMountProps = + new SparseArrayCompat<>(); + private final int mSurfaceId; public SurfaceMountingManager( @@ -682,13 +694,110 @@ public void createViewUnsafe( } } + private static void overridePropsReadableMap( + Map patchMap, WritableMap outputReadableMap) { + for (Map.Entry entry : patchMap.entrySet()) { + String propKey = entry.getKey(); + if (outputReadableMap.hasKey(propKey)) { + Object propValue = entry.getValue(); + if (propKey.equals("transform")) { + assert (outputReadableMap.getType(propKey) == ReadableType.Array + && propValue instanceof ArrayList); + WritableArray array = new WritableNativeArray(); + for (Object item : (ArrayList) propValue) { + if (item instanceof HashMap) { + WritableNativeMap itemMap = new WritableNativeMap(); + for (Map.Entry itemEntry : + ((HashMap) item).entrySet()) { + if (itemEntry.getValue() instanceof String) { + itemMap.putString(itemEntry.getKey(), (String) itemEntry.getValue()); + } else if (itemEntry.getValue() instanceof Number) { + itemMap.putDouble( + itemEntry.getKey(), ((Number) itemEntry.getValue()).doubleValue()); + } + } + array.pushMap(itemMap); + } + } + outputReadableMap.putArray(propKey, array); + } else if (propKey.equals("opacity")) { + assert (outputReadableMap.getType(propKey) == ReadableType.Number + && propValue instanceof Number); + outputReadableMap.putDouble(propKey, ((Number) propValue).doubleValue()); + } + } + } + } + + private static Map getHashMapFromPropsReadableMap(ReadableMap readableMap) { + HashMap outputMap = new HashMap<>(); + + Iterator> iter = readableMap.getEntryIterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + String propKey = entry.getKey(); + Object propValue = entry.getValue(); + if (propKey.equals("transform") && propValue instanceof ReadableArray) { + ArrayList> arrayList = new ArrayList<>(); + for (int i = 0; i < ((ReadableArray) propValue).size(); i++) { + ReadableMap map = ((ReadableArray) propValue).getMap(i); + if (map != null) { + arrayList.add(map.toHashMap()); + } + } + outputMap.put(propKey, arrayList); + } else if (propKey.equals("opacity") && propValue instanceof Number) { + outputMap.put(propKey, ((Number) propValue).doubleValue()); + } + } + + return outputMap; + } + + public void storeSynchronousMountPropsOverride(int reactTag, ReadableMap props) { + if (ReactNativeFeatureFlags.overrideBySynchronousMountPropsAtMountingAndroid()) { + Map propsMap = getHashMapFromPropsReadableMap(props); + if (mTagToSynchronousMountProps.containsKey(reactTag)) { + Map mergedPropsMap = + Assertions.assertNotNull(mTagToSynchronousMountProps.get(reactTag)); + mergedPropsMap.putAll(propsMap); + mTagToSynchronousMountProps.put(reactTag, mergedPropsMap); + } else { + mTagToSynchronousMountProps.put(reactTag, propsMap); + } + } + } + + public void updatePropsSynchronously(int reactTag, ReadableMap props) { + updateProps(reactTag, props, true); + } + public void updateProps(int reactTag, ReadableMap props) { + updateProps(reactTag, props, false); + } + + @UiThread + private void updateProps( + int reactTag, ReadableMap props, Boolean shouldSkipSynchronousMountPropsOverride) { if (isStopped()) { return; } ViewState viewState = getViewState(reactTag); - viewState.mCurrentProps = new ReactStylesDiffMap(props); + + if (ReactNativeFeatureFlags.overrideBySynchronousMountPropsAtMountingAndroid() + && !shouldSkipSynchronousMountPropsOverride + && mTagToSynchronousMountProps.containsKey(reactTag)) { + WritableMap modifiedProps = new WritableNativeMap(); + modifiedProps.merge(props); + Map directPropsMap = + Assertions.assertNotNull(mTagToSynchronousMountProps.get(reactTag)); + overridePropsReadableMap(directPropsMap, modifiedProps); + viewState.mCurrentProps = new ReactStylesDiffMap(modifiedProps); + } else { + viewState.mCurrentProps = new ReactStylesDiffMap(props); + } + View view = viewState.mView; if (view == null) { @@ -1057,6 +1166,11 @@ public void deleteView(int reactTag) { return; } + if (ReactNativeFeatureFlags.overrideBySynchronousMountPropsAtMountingAndroid() + && mTagToSynchronousMountProps.containsKey(reactTag)) { + mTagToSynchronousMountProps.remove(reactTag); + } + ViewState viewState = getNullableViewState(reactTag); if (viewState == null) { diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index b2fecda32d6f..c4960f1fc19e 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -742,7 +742,8 @@ folly::dynamic NativeAnimatedNodesManager::managedProps( if (const auto node = getAnimatedNode(iter->second)) { return node->props(); } - } else { + } else if (!ReactNativeFeatureFlags:: + overrideBySynchronousMountPropsAtMountingAndroid()) { std::lock_guard lockUnsyncedDirectViewProps( unsyncedDirectViewPropsMutex_); if (auto it = unsyncedDirectViewProps_.find(tag); @@ -761,7 +762,8 @@ bool NativeAnimatedNodesManager::hasManagedProps() const noexcept { return true; } } - { + if (!ReactNativeFeatureFlags:: + overrideBySynchronousMountPropsAtMountingAndroid()) { std::lock_guard lock(unsyncedDirectViewPropsMutex_); if (!unsyncedDirectViewProps_.empty()) { return true; @@ -771,10 +773,13 @@ bool NativeAnimatedNodesManager::hasManagedProps() const noexcept { } void NativeAnimatedNodesManager::onManagedPropsRemoved(Tag tag) noexcept { - std::lock_guard lock(unsyncedDirectViewPropsMutex_); - if (auto iter = unsyncedDirectViewProps_.find(tag); - iter != unsyncedDirectViewProps_.end()) { - unsyncedDirectViewProps_.erase(iter); + if (!ReactNativeFeatureFlags:: + overrideBySynchronousMountPropsAtMountingAndroid()) { + std::lock_guard lock(unsyncedDirectViewPropsMutex_); + if (auto iter = unsyncedDirectViewProps_.find(tag); + iter != unsyncedDirectViewProps_.end()) { + unsyncedDirectViewProps_.erase(iter); + } } } @@ -828,7 +833,8 @@ void NativeAnimatedNodesManager::schedulePropsCommit( mergeObjects(updateViewPropsDirect_[viewTag], props); } else if (!layoutStyleUpdated && directManipulationCallback_ != nullptr) { mergeObjects(updateViewPropsDirect_[viewTag], props); - { + if (!ReactNativeFeatureFlags:: + overrideBySynchronousMountPropsAtMountingAndroid()) { std::lock_guard lock(unsyncedDirectViewPropsMutex_); mergeObjects(unsyncedDirectViewProps_[viewTag], props); } From 44b2da0df2b4121d4d160cb35d0b5d7b05704698 Mon Sep 17 00:00:00 2001 From: Mark Verlingieri Date: Fri, 5 Sep 2025 11:58:48 -0700 Subject: [PATCH 0067/1110] Revert D81766029: Make feature flag "enableViewRecyclingForScrollView" true by default Differential Revision: D81766029 Original commit changeset: df4a260b9bde Original Phabricator Diff: D81766029 fbshipit-source-id: f1cd2a2cef632cfe9cbf0cd13e9ec20cfa5cc6ae --- .../internal/featureflags/ReactNativeFeatureFlagsDefaults.kt | 4 ++-- .../react/featureflags/ReactNativeFeatureFlagsDefaults.h | 4 ++-- .../scripts/featureflags/ReactNativeFeatureFlags.config.js | 2 +- .../src/private/featureflags/ReactNativeFeatureFlags.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 122dea6b3c5f..b442387b1b22 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<88256ff0f51c33caee5af50bb3424288>> + * @generated SignedSource<<7c4c3bf6b720f3e15fb97d8f1a4d74ba>> */ /** @@ -101,7 +101,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableViewRecyclingForImage(): Boolean = true - override fun enableViewRecyclingForScrollView(): Boolean = true + override fun enableViewRecyclingForScrollView(): Boolean = false override fun enableViewRecyclingForText(): Boolean = true diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index f1f08c65ee76..e9f2bfbd67be 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<68b40caf4c7d5d92cb5d5eb7bde74ab3>> */ /** @@ -184,7 +184,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { } bool enableViewRecyclingForScrollView() override { - return true; + return false; } bool enableViewRecyclingForText() override { diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 3fdcc220e80e..603bf9beec6d 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -463,7 +463,7 @@ const definitions: FeatureFlagDefinitions = { ossReleaseStage: 'none', }, enableViewRecyclingForScrollView: { - defaultValue: true, + defaultValue: false, metadata: { dateAdded: '2025-08-20', description: diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 331b5716ec8a..d3a774635231 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8b7c4c7178448a1ea3f55b4cdbe3b95e>> + * @generated SignedSource<<1a00c3c154cd9e8afac7ad9333152ac6>> * @flow strict * @noformat */ @@ -360,7 +360,7 @@ export const enableViewRecyclingForImage: Getter = createNativeFlagGett /** * Enables View Recycling for via ReactViewGroup/ReactViewManager. */ -export const enableViewRecyclingForScrollView: Getter = createNativeFlagGetter('enableViewRecyclingForScrollView', true); +export const enableViewRecyclingForScrollView: Getter = createNativeFlagGetter('enableViewRecyclingForScrollView', false); /** * Enables View Recycling for via ReactTextView/ReactTextViewManager. */ From 02e3a999ed1c59b4dc0d5f925af94c0c5b117d57 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 5 Sep 2025 20:37:58 -0700 Subject: [PATCH 0068/1110] Back out "Use uint32_t as internal Color representation" (#53622) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53622 Reverting to avoid lossy conversion on hosts expecting signed int values for color conversion. Changelog: [Internal] Reviewed By: rozele, javache Differential Revision: D81785268 fbshipit-source-id: 4a8d099e378fa55e76a58c2ab0356d88e344de2c --- .../components/view/HostPlatformViewProps.cpp | 2 +- .../renderer/graphics/HostPlatformColor.h | 2 +- .../renderer/graphics/PlatformColorParser.h | 2 +- .../renderer/graphics/HostPlatformColor.h | 2 +- .../renderer/graphics/HostPlatformColor.h | 16 +++++++------- .../renderer/graphics/HostPlatformColor.mm | 22 +++++++++---------- .../react/devsupport/DevLoadingViewModule.cpp | 8 +++---- .../react/devsupport/DevLoadingViewModule.h | 4 ++-- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index 30ae94e6b5b2..69eae0bfd0c7 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -310,7 +310,7 @@ static void updateBorderColorPropValue( const std::optional& newColor, const std::optional& oldColor) { if (newColor != oldColor) { - result[propName] = *newColor.value_or(SharedColor()); + result[propName] = newColor.has_value() ? *newColor.value() : NULL; } } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h index e4f0e259443d..9752be6b7f0c 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h @@ -12,7 +12,7 @@ namespace facebook::react { -using Color = uint32_t; +using Color = int32_t; namespace HostPlatformColor { constexpr facebook::react::Color UndefinedColor = 0; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h index 4a29559a848f..ca11a1c7deef 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h @@ -42,7 +42,7 @@ inline SharedColor parsePlatformColor( auto color = getColorFromJava(fabricUIManager, surfaceId, *javaResourcePaths); - auto argb = (uint32_t)color; + auto argb = (int64_t)color; auto ratio = 255.f; colorComponents.alpha = ((argb >> 24) & 0xFF) / ratio; colorComponents.red = ((argb >> 16) & 0xFF) / ratio; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/HostPlatformColor.h index f49461e08297..3bbcb1e11ea1 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/HostPlatformColor.h @@ -13,7 +13,7 @@ namespace facebook::react { -using Color = uint32_t; +using Color = int32_t; namespace HostPlatformColor { constexpr facebook::react::Color UndefinedColor = 0; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h index d657394121b1..1979a74ed561 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h @@ -14,18 +14,18 @@ namespace facebook::react { struct DynamicColor { - uint32_t lightColor = 0; - uint32_t darkColor = 0; - uint32_t highContrastLightColor = 0; - uint32_t highContrastDarkColor = 0; + int32_t lightColor = 0; + int32_t darkColor = 0; + int32_t highContrastLightColor = 0; + int32_t highContrastDarkColor = 0; }; struct Color { - Color(uint32_t color); + Color(int32_t color); Color(const DynamicColor& dynamicColor); Color(const ColorComponents& components); Color() : uiColor_(nullptr){}; - uint32_t getColor() const; + int32_t getColor() const; std::size_t getUIColorHash() const; static Color createSemanticColor(std::vector& semanticItems); @@ -38,7 +38,7 @@ struct Color { ColorComponents getColorComponents() const { float ratio = 255; - uint32_t primitiveColor = getColor(); + int32_t primitiveColor = getColor(); return ColorComponents{ .red = (float)((primitiveColor >> 16) & 0xff) / ratio, .green = (float)((primitiveColor >> 8) & 0xff) / ratio, @@ -47,7 +47,7 @@ struct Color { } bool operator==(const Color& other) const; bool operator!=(const Color& other) const; - operator uint32_t() const { + operator int32_t() const { return getColor(); } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm index a92380ce282e..93ecb7e33190 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm @@ -36,7 +36,7 @@ bool UIColorIsP3ColorSpace(const std::shared_ptr &uiColor) return false; } -UIColor *_Nullable UIColorFromInt32(uint32_t intColor) +UIColor *_Nullable UIColorFromInt32(int32_t intColor) { CGFloat a = CGFloat((intColor >> 24) & 0xFF) / 255.0; CGFloat r = CGFloat((intColor >> 16) & 0xFF) / 255.0; @@ -49,10 +49,10 @@ bool UIColorIsP3ColorSpace(const std::shared_ptr &uiColor) UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &dynamicColor) { - uint32_t light = dynamicColor.lightColor; - uint32_t dark = dynamicColor.darkColor; - uint32_t highContrastLight = dynamicColor.highContrastLightColor; - uint32_t highContrastDark = dynamicColor.highContrastDarkColor; + int32_t light = dynamicColor.lightColor; + int32_t dark = dynamicColor.darkColor; + int32_t highContrastLight = dynamicColor.highContrastLightColor; + int32_t highContrastDark = dynamicColor.highContrastDarkColor; UIColor *lightColor = UIColorFromInt32(light); UIColor *darkColor = UIColorFromInt32(dark); @@ -83,7 +83,7 @@ bool UIColorIsP3ColorSpace(const std::shared_ptr &uiColor) return nil; } -uint32_t ColorFromColorComponents(const facebook::react::ColorComponents &components) +int32_t ColorFromColorComponents(const facebook::react::ColorComponents &components) { float ratio = 255; auto color = ((int32_t)round((float)components.alpha * ratio) & 0xff) << 24 | @@ -92,7 +92,7 @@ uint32_t ColorFromColorComponents(const facebook::react::ColorComponents &compon return color; } -uint32_t ColorFromUIColor(UIColor *color) +int32_t ColorFromUIColor(UIColor *color) { CGFloat rgba[4]; [color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]]; @@ -100,7 +100,7 @@ uint32_t ColorFromUIColor(UIColor *color) {.red = (float)rgba[0], .green = (float)rgba[1], .blue = (float)rgba[2], .alpha = (float)rgba[3]}); } -uint32_t ColorFromUIColorForSpecificTraitCollection( +int32_t ColorFromUIColorForSpecificTraitCollection( const std::shared_ptr &uiColor, UITraitCollection *traitCollection) { @@ -113,7 +113,7 @@ uint32_t ColorFromUIColorForSpecificTraitCollection( return 0; } -uint32_t ColorFromUIColor(const std::shared_ptr &uiColor) +int32_t ColorFromUIColor(const std::shared_ptr &uiColor) { return ColorFromUIColorForSpecificTraitCollection(uiColor, [UITraitCollection currentTraitCollection]); } @@ -172,7 +172,7 @@ uint32_t ColorFromUIColor(const std::shared_ptr &uiColor) } // anonymous namespace -Color::Color(uint32_t color) +Color::Color(int32_t color) { uiColor_ = wrapManagedObject(UIColorFromInt32(color)); uiColorHashValue_ = facebook::react::hash_combine(color, 0); @@ -217,7 +217,7 @@ uint32_t ColorFromUIColor(const std::shared_ptr &uiColor) return !(*this == other); } -uint32_t Color::getColor() const +int32_t Color::getColor() const { return ColorFromUIColor(uiColor_); } diff --git a/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.cpp b/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.cpp index 0836032adc81..637c4d29edcf 100644 --- a/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.cpp +++ b/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.cpp @@ -9,8 +9,8 @@ namespace facebook::react { -const uint32_t DEFAULT_TEXT_COLOR = 0xFFFFFFFF; -const uint32_t DEFAULT_BACKGROUND_COLOR = 0xFF2584E8; +const int32_t DEFAULT_TEXT_COLOR = 0xFFFFFFFF; +const int32_t DEFAULT_BACKGROUND_COLOR = 0xFF2584E8; DevLoadingViewModule::DevLoadingViewModule( std::shared_ptr jsInvoker, @@ -27,8 +27,8 @@ DevLoadingViewModule::~DevLoadingViewModule() { void DevLoadingViewModule::showMessage( jsi::Runtime& /*rt*/, const std::string& message, - std::optional textColor, - std::optional backgroundColor) { + std::optional textColor, + std::optional backgroundColor) { if (auto devUIDelegate = devUIDelegate_.lock()) { devUIDelegate->showLoadingView( message, diff --git a/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.h b/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.h index 2b97e74361f7..9fa51278f7c4 100644 --- a/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.h +++ b/packages/react-native/ReactCxxPlatform/react/devsupport/DevLoadingViewModule.h @@ -27,8 +27,8 @@ class DevLoadingViewModule void showMessage( jsi::Runtime& rt, const std::string& message, - std::optional textColor, - std::optional backgroundColor); + std::optional textColor, + std::optional backgroundColor); void hide(jsi::Runtime& rt); From c04248dc5c9dbc94c43c8070c0de331793416258 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Mon, 8 Sep 2025 03:48:46 -0700 Subject: [PATCH 0069/1110] chore: Add Bluesky badge to README (#53616) Summary: Add Bluesky badge to README. ## Changelog: [INTERNAL][ADDED] Add Bluesky badge to README Pull Request resolved: https://github.com/facebook/react-native/pull/53616 Test Plan: The updated README has been checked out on the PR branch. ## Preview: Screenshot 2025-09-05 at 13 00 34 Reviewed By: cipolleschi Differential Revision: D81909921 Pulled By: cortinico fbshipit-source-id: b5fa79f152f126944bc1c31c8fbb86889f0f25db --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 502b38bcd2a4..b4280bb6abd8 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,13 @@ Current npm package version. - PRs welcome! + PRs are welcome! - Follow @reactnative + Follow @reactnative on X + + + Follow @reactnative.dev on Bluesky

From e7aeea26bde6e9cda0a3a0a55fc2a0421fb0c0e5 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Mon, 8 Sep 2025 04:30:21 -0700 Subject: [PATCH 0070/1110] Deprecate legacy javascript apis (#53630) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53630 These JavaScript apis were a part of react native's legacy architecture. Let's deprecate them, so that we can eventually remove them in the future. Changelog: [General][Deprecated] - Deprecate legacy javascript react native apis Reviewed By: cortinico Differential Revision: D81795732 fbshipit-source-id: 0a2bd142fa7e08c1f3daaa437ee127a2156e045b --- packages/react-native/Libraries/BatchedBridge/BatchedBridge.js | 1 + packages/react-native/Libraries/BatchedBridge/MessageQueue.js | 1 + packages/react-native/Libraries/Core/Timers/JSTimers.js | 1 + packages/react-native/Libraries/Core/Timers/NativeTiming.js | 1 + packages/react-native/Libraries/Core/Timers/immediateShim.js | 1 + .../src/private/specs_DEPRECATED/modules/NativeTiming.js | 1 + 6 files changed, 6 insertions(+) diff --git a/packages/react-native/Libraries/BatchedBridge/BatchedBridge.js b/packages/react-native/Libraries/BatchedBridge/BatchedBridge.js index 5a121a27f7f7..07fa1c73451f 100644 --- a/packages/react-native/Libraries/BatchedBridge/BatchedBridge.js +++ b/packages/react-native/Libraries/BatchedBridge/BatchedBridge.js @@ -6,6 +6,7 @@ * * @flow strict * @format + * @deprecated */ 'use strict'; diff --git a/packages/react-native/Libraries/BatchedBridge/MessageQueue.js b/packages/react-native/Libraries/BatchedBridge/MessageQueue.js index 2e6bd306669d..9b45ef381180 100644 --- a/packages/react-native/Libraries/BatchedBridge/MessageQueue.js +++ b/packages/react-native/Libraries/BatchedBridge/MessageQueue.js @@ -6,6 +6,7 @@ * * @flow strict * @format + * @deprecated */ 'use strict'; diff --git a/packages/react-native/Libraries/Core/Timers/JSTimers.js b/packages/react-native/Libraries/Core/Timers/JSTimers.js index d2922e4cd49b..4ef450e0bc13 100644 --- a/packages/react-native/Libraries/Core/Timers/JSTimers.js +++ b/packages/react-native/Libraries/Core/Timers/JSTimers.js @@ -6,6 +6,7 @@ * * @flow * @format + * @deprecated */ import NativeTiming from './NativeTiming'; diff --git a/packages/react-native/Libraries/Core/Timers/NativeTiming.js b/packages/react-native/Libraries/Core/Timers/NativeTiming.js index 89dc56bd63e1..a31bdba18928 100644 --- a/packages/react-native/Libraries/Core/Timers/NativeTiming.js +++ b/packages/react-native/Libraries/Core/Timers/NativeTiming.js @@ -6,6 +6,7 @@ * * @flow strict * @format + * @deprecated */ export * from '../../../src/private/specs_DEPRECATED/modules/NativeTiming'; diff --git a/packages/react-native/Libraries/Core/Timers/immediateShim.js b/packages/react-native/Libraries/Core/Timers/immediateShim.js index ef4e87dff862..08a1f8ce9951 100644 --- a/packages/react-native/Libraries/Core/Timers/immediateShim.js +++ b/packages/react-native/Libraries/Core/Timers/immediateShim.js @@ -6,6 +6,7 @@ * * @flow * @format + * @deprecated */ 'use strict'; diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeTiming.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeTiming.js index 9d6c5806d02a..d781403f893e 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeTiming.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeTiming.js @@ -6,6 +6,7 @@ * * @flow strict * @format + * @deprecated */ import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport'; From 2152180fa0e6fcd3a777db684698b716bcd64c86 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Mon, 8 Sep 2025 06:54:09 -0700 Subject: [PATCH 0071/1110] Minor bump memfs dev dependency (#53628) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53628 Bump the `memfs` dependency used in tests to the latest minor - there have been a considerable number of updates since 4.7 including support for various newer (and some old) Node fs APIs: https://github.com/streamich/memfs/blob/master/CHANGELOG.md Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D81879137 fbshipit-source-id: e75946dac100809cb39c88971fd6ed397dc9f49e --- package.json | 2 +- yarn.lock | 77 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a9349970d375..a195a57d6bdc 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "jsonc-parser": "2.2.1", "markdownlint-cli2": "^0.17.2", "markdownlint-rule-relative-links": "^3.0.0", - "memfs": "^4.7.7", + "memfs": "^4.38.2", "metro-babel-register": "^0.83.1", "metro-transform-plugins": "^0.83.1", "micromatch": "^4.0.4", diff --git a/yarn.lock b/yarn.lock index dc7be140a75f..b84f78465c83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1553,6 +1553,50 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsonjoy.com/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" + integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== + +"@jsonjoy.com/buffers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz#ade6895b7d3883d70f87b5743efaa12c71dfef7a" + integrity sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q== + +"@jsonjoy.com/codegen@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207" + integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== + +"@jsonjoy.com/json-pack@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.11.0.tgz#3d40d3d8042f5e9eeb005658a76b788e8ca84ac0" + integrity sha512-nLqSTAYwpk+5ZQIoVp7pfd/oSKNWlEdvTq2LzVA4r2wtWZg6v+5u0VgBOaDJuUfNOuw/4Ysq6glN5QKSrOCgrA== + dependencies: + "@jsonjoy.com/base64" "^1.1.2" + "@jsonjoy.com/buffers" "^1.0.0" + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/json-pointer" "^1.0.1" + "@jsonjoy.com/util" "^1.9.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + +"@jsonjoy.com/json-pointer@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz#049cb530ac24e84cba08590c5e36b431c4843408" + integrity sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg== + dependencies: + "@jsonjoy.com/codegen" "^1.0.0" + "@jsonjoy.com/util" "^1.9.0" + +"@jsonjoy.com/util@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46" + integrity sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ== + dependencies: + "@jsonjoy.com/buffers" "^1.0.0" + "@jsonjoy.com/codegen" "^1.0.0" + "@malept/cross-spawn-promise@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz#d0772de1aa680a0bfb9ba2f32b4c828c7857cb9d" @@ -4977,6 +5021,11 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob-to-regex.js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz#f71cc9cb8441471a9318626160bc8a35e1306b21" + integrity sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg== + glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -5255,6 +5304,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +hyperdyperid@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" + integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== + hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" @@ -6688,11 +6742,16 @@ memfs-or-file-map-to-github-branch@^1.3.0: dependencies: "@octokit/rest" "*" -memfs@^4.7.7: - version "4.7.7" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.7.7.tgz#bcf09cab1646d655f659e7cf832dfc75ccb95b2d" - integrity sha512-x9qc6k88J/VVwnfTkJV8pRRswJ2156Rc4w5rciRqKceFDZ0y1MqsNL9pkg5sE0GOcDzZYbonreALhaHzg1siFw== +memfs@^4.38.2: + version "4.38.2" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.38.2.tgz#e3a3a0362032c3ab7093cc7c179bd5fa8abc94c3" + integrity sha512-FpWsVHpAkoSh/LfY1BgAl72BVd374ooMRtDi2VqzBycX4XEfvC0XKACCe0C9VRZoYq5viuoyTv6lYXZ/Q7TrLQ== dependencies: + "@jsonjoy.com/json-pack" "^1.11.0" + "@jsonjoy.com/util" "^1.9.0" + glob-to-regex.js "^1.0.1" + thingies "^2.5.0" + tree-dump "^1.0.3" tslib "^2.0.0" memoize-one@^5.0.0: @@ -8869,6 +8928,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thingies@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/thingies/-/thingies-2.5.0.tgz#5f7b882c933b85989f8466b528a6247a6881e04f" + integrity sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw== + throat@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" @@ -8918,6 +8982,11 @@ traverse@0.6.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha512-kdf4JKs8lbARxWdp7RKdNzoJBhGUcIalSYibuGyHJbmk40pOysQ0+QPvlkCOICOivDWU2IJo2rkrxyTK2AH4fw== +tree-dump@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4" + integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== + trim-repeated@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" From d0fb33822de38183895c8b3421f46966909beaf3 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Mon, 8 Sep 2025 07:16:48 -0700 Subject: [PATCH 0072/1110] Check value of the Hermes V1 flag instead of whether it's defined (#53637) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53637 Changelog: [ANDROID][FIXED] - Check for the value of the HERMES_V1_ENABLED flag instead of whether it's defined Reviewed By: cortinico Differential Revision: D81920483 fbshipit-source-id: 550ae9fd27f666affe102b1c5c3f51bde7b5923e --- .../src/main/jni/react/hermes/reactexecutor/CMakeLists.txt | 2 +- .../src/main/jni/react/runtime/hermes/jni/CMakeLists.txt | 2 +- .../ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt | 2 +- .../react-native/ReactCommon/hermes/executor/CMakeLists.txt | 2 +- .../ReactCommon/hermes/inspector-modern/CMakeLists.txt | 2 +- packages/react-native/ReactCommon/react/runtime/CMakeLists.txt | 2 +- .../ReactCommon/react/runtime/hermes/CMakeLists.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt index a73ab1c5297b..b28df6a8a2fc 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt @@ -28,7 +28,7 @@ target_compile_reactnative_options(hermes_executor PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(hermes_executor PRIVATE -DHERMES_ENABLE_DEBUGGER=1) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(hermes_executor PRIVATE -DHERMES_V1_ENABLED=1) endif() endif() diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt index 42612452f2e2..3ec451152cf2 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt @@ -30,7 +30,7 @@ target_compile_reactnative_options(hermesinstancejni PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(hermesinstancejni PRIVATE -DHERMES_ENABLE_DEBUGGER=1) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(hermesinstancejni PRIVATE -DHERMES_V1_ENABLED=1) endif() endif () diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt index 8b88a0c2c9e6..5394cb4af015 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt @@ -20,7 +20,7 @@ target_compile_reactnative_options(rninstance PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(rninstance PRIVATE -DHERMES_ENABLE_DEBUGGER=1) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(rninstance PRIVATE -DHERMES_V1_ENABLED=1) endif() endif () diff --git a/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt b/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt index 741ced76e886..cf991e1729b0 100644 --- a/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt +++ b/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt @@ -33,7 +33,7 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) -DHERMES_ENABLE_DEBUGGER=1 ) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(hermes_executor_common PRIVATE -DHERMES_V1_ENABLED=1) endif() else() diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt b/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt index 43ceec656215..d392615a80e9 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt @@ -24,7 +24,7 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) -DHERMES_ENABLE_DEBUGGER=1 ) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(hermes_inspector_modern PRIVATE -DHERMES_V1_ENABLED=1) endif() endif() diff --git a/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt index 3a4e6360d3d8..25927837d9b6 100644 --- a/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt @@ -19,7 +19,7 @@ target_compile_reactnative_options(bridgeless PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(bridgeless PRIVATE -DHERMES_ENABLE_DEBUGGER=1) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(bridgeless PRIVATE -DHERMES_V1_ENABLED=1) endif() endif () diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt index c5bf10464e08..a237dfed110d 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt @@ -36,7 +36,7 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) -DHERMES_ENABLE_DEBUGGER=1 ) - if (DEFINED HERMES_V1_ENABLED) + if (HERMES_V1_ENABLED) target_compile_options(bridgelesshermes PRIVATE -DHERMES_V1_ENABLED=1) endif() endif() From d0140ce53bb55b2812d5657c9d5154abcc2114a8 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Mon, 8 Sep 2025 08:43:40 -0700 Subject: [PATCH 0073/1110] enable opt-in for enableDefaultTransitionIndicator (#34373) Summary: So we can test the feature. DiffTrain build for [3168e08f8389d258de9eb7c8d19b9d44a0f250f2](https://github.com/facebook/react/commit/3168e08f8389d258de9eb7c8d19b9d44a0f250f2) Reviewed By: kassens Differential Revision: D81599263 fbshipit-source-id: a33ca01250206a2a35350f7fad09e43071522df7 --- .../react-native/Libraries/Renderer/shims/ReactNativeTypes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js b/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js index 55f5187b3cb8..bd5cf5eaace7 100644 --- a/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js +++ b/packages/react-native/Libraries/Renderer/shims/ReactNativeTypes.js @@ -7,7 +7,7 @@ * @noformat * @nolint * @flow strict - * @generated SignedSource<> + * @generated SignedSource<> */ import type { @@ -135,6 +135,7 @@ export type RenderRootOptions = { error: mixed, errorInfo: {+componentStack?: ?string}, ) => void, + onDefaultTransitionIndicator?: () => void | (() => void), }; /** From 13120f630d4979f12f244b1b9ac9ef5e093d2251 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 8 Sep 2025 09:09:23 -0700 Subject: [PATCH 0074/1110] Remove unnecessary extra quote on hermesVersionProvider (#53641) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53641 This extra quote is causing the build on the 0.82-stable to fail. The reason is that the Path for the `.hermesversion` file is composed wrongly so we attempt to build hermes from the `main` branch. Changelog: [Internal] [Changed] - Created from CodeHub with https://fburl.com/edit-in-codehub Reviewed By: j-piasecki, vzaidman Differential Revision: D81925624 fbshipit-source-id: 700f9d44b6c7efdb845232dad8ca7c2e3136385d --- .../react-native/ReactAndroid/hermes-engine/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts b/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts index 6e0b447f15ec..c59c7c4885f1 100644 --- a/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts +++ b/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts @@ -88,7 +88,7 @@ val hermesVersionProvider: Provider = val hermesVersionFile = File( reactNativeRootDir, - if (hermesV1Enabled) "sdks/.hermesv1version" else "\"sdks/.hermesversion\"", + if (hermesV1Enabled) "sdks/.hermesv1version" else "sdks/.hermesversion", ) if (hermesVersionFile.exists()) { From 49be01add1ac0f84bf34fff579a5d6588b334a22 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 8 Sep 2025 10:27:24 -0700 Subject: [PATCH 0075/1110] Move headers from .h to .mm file (#53617) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53617 In the RCTAppDelegate.h file there are a couple of headers that are not used and that can be either removed or moved to the .mm file. This reduce the coupling between the AppDelegate library and React Core and allow us to reduce the size of the exported headers in the umbrella header. ## Changelog: [Internal] - Reviewed By: cortinico Differential Revision: D81769485 fbshipit-source-id: b811dde0331e8a668618e0c8eb250fd81bf48545 --- packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h | 2 -- packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index 890bb99d4562..66d64d22044d 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -#import -#import #import #import "RCTDefaultReactNativeFactoryDelegate.h" #import "RCTReactNativeFactory.h" diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 05fb5a0c4b78..8abc2fc431bf 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -6,6 +6,7 @@ */ #import "RCTAppDelegate.h" +#import #import #import #import From e7ce4ff0bfdc469a40c3bd66edadaaf6c62e2ec3 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Mon, 8 Sep 2025 13:35:23 -0700 Subject: [PATCH 0076/1110] Move NetworkReporter out of jsinspector-modern (#53484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53484 Refactor to better organise network event reporting features. - Introduce new `ReactCommon/react/networking` package, containing `NetworkReporter` class (outer-most interface with each platform). - Move `ReactCommon/performance/timeline` dependency to this level, removing jsinspector→performance dependency. - Simplifies the remaining `NetworkHandler` in `jsinspector-modern/network` — which now is only focused on CDP network reporting. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D81129562 fbshipit-source-id: 6c36045e872b0fd9510d0fa3e98acb0969e74d72 --- .../Network/RCTInspectorNetworkReporter.mm | 4 +- packages/react-native/Package.swift | 14 + .../React/React-RCTFabric.podspec | 2 +- .../ReactAndroid/src/main/jni/CMakeLists.txt | 3 + .../main/jni/react/devsupport/CMakeLists.txt | 2 +- .../devsupport/JInspectorNetworkReporter.cpp | 2 +- .../jsinspector-modern/NetworkIOAgent.cpp | 16 +- .../jsinspector-modern/network/CMakeLists.txt | 4 +- .../jsinspector-modern/network/CdpNetwork.cpp | 23 +- .../jsinspector-modern/network/CdpNetwork.h | 14 +- .../jsinspector-modern/network/HttpUtils.cpp | 3 +- .../jsinspector-modern/network/HttpUtils.h | 6 +- .../network/NetworkHandler.cpp | 209 ++++++++++++ .../network/NetworkHandler.h | 147 ++++++++ .../network/NetworkReporter.cpp | 316 ------------------ .../network/React-jsinspectornetwork.podspec | 3 - .../react/networking/CMakeLists.txt | 21 ++ .../react/networking/NetworkReporter.cpp | 192 +++++++++++ .../networking}/NetworkReporter.h | 65 +--- .../networking}/NetworkTypes.h | 4 +- .../react/networking/React-networking.podspec | 51 +++ .../react-native/scripts/react_native_pods.rb | 1 + 22 files changed, 674 insertions(+), 428 deletions(-) create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h delete mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp create mode 100644 packages/react-native/ReactCommon/react/networking/CMakeLists.txt create mode 100644 packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp rename packages/react-native/ReactCommon/{jsinspector-modern/network => react/networking}/NetworkReporter.h (70%) rename packages/react-native/ReactCommon/{jsinspector-modern/network => react/networking}/NetworkTypes.h (88%) create mode 100644 packages/react-native/ReactCommon/react/networking/React-networking.podspec diff --git a/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm b/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm index 74d8155832cd..96a73d19aa00 100644 --- a/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm +++ b/packages/react-native/Libraries/Network/RCTInspectorNetworkReporter.mm @@ -10,9 +10,9 @@ #import "RCTNetworkConversions.h" #import -#import +#import -using namespace facebook::react::jsinspector_modern; +using namespace facebook::react; #ifdef REACT_NATIVE_DEBUGGER_ENABLED namespace { diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index a756decbf4b5..4617284315d0 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -211,6 +211,18 @@ let reactHermes = RNTarget( ] ) +/// React-networking.podspec +let reactNetworking = RNTarget( + name: .reactNetworking, + path: "ReactCommon/react/networking", + excludedPaths: ["tests"], + dependencies: [.reactNativeDependencies, .reactJsInspectorNetwork, .reactPerformanceTimeline], + defines: [ + CXXSetting.define("REACT_NATIVE_DEBUGGER_ENABLED", to: "1", .when(configuration: BuildConfiguration.debug)), + CXXSetting.define("REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY", to: "1", .when(configuration: BuildConfiguration.debug)), + ] +) + /// React-performancecdpmetrics.podspec let reactPerformanceCdpMetrics = RNTarget( name: .reactPerformanceCdpMetrics, @@ -566,6 +578,7 @@ let targets = [ reactNativeDependencies, hermesPrebuilt, reactJsiTooling, + reactNetworking, reactPerformanceCdpMetrics, reactPerformanceTimeline, reactRuntimeScheduler, @@ -737,6 +750,7 @@ extension String { static let hermesPrebuilt = "hermes-prebuilt" static let reactJsiTooling = "React-jsitooling" + static let reactNetworking = "React-networking" static let reactPerformanceCdpMetrics = "React-performancecdpmetrics" static let reactPerformanceTimeline = "React-performancetimeline" static let reactRuntimeScheduler = "React-runtimescheduler" diff --git a/packages/react-native/React/React-RCTFabric.podspec b/packages/react-native/React/React-RCTFabric.podspec index 83000572e5ba..385b3480d615 100644 --- a/packages/react-native/React/React-RCTFabric.podspec +++ b/packages/react-native/React/React-RCTFabric.podspec @@ -91,9 +91,9 @@ Pod::Spec.new do |s| add_dependency(s, "React-RCTAnimation", :framework_name => 'RCTAnimation') add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern') add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp') - add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork') add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing') add_dependency(s, "React-performancecdpmetrics", :framework_name => 'React_performancecdpmetrics') + add_dependency(s, "React-networking", :framework_name => 'React_networking') add_dependency(s, "React-renderercss") add_dependency(s, "React-RCTFBReactNativeSpec") diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index e28541f17c68..dec36bef5f2c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -124,6 +124,7 @@ add_react_common_subdir(react/nativemodule/dom) add_react_common_subdir(react/nativemodule/featureflags) add_react_common_subdir(react/nativemodule/microtasks) add_react_common_subdir(react/nativemodule/idlecallbacks) +add_react_common_subdir(react/networking) add_react_common_subdir(jserrorhandler) add_react_common_subdir(react/runtime) add_react_common_subdir(react/runtime/hermes) @@ -190,6 +191,7 @@ add_library(reactnative $ $ $ + $ $ $ $ @@ -279,6 +281,7 @@ target_include_directories(reactnative $ $ $ + $ $ $ $ diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt index cddaf86af7df..3905a4af4ad1 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt @@ -20,6 +20,6 @@ target_include_directories(react_devsupportjni PUBLIC .) target_link_libraries(react_devsupportjni fbjni jsinspector - jsinspector_network) + react_networking) target_compile_reactnative_options(react_devsupportjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp index 34eda93de81c..f8cd1479cb3a 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JInspectorNetworkReporter.cpp @@ -7,7 +7,7 @@ #include "JInspectorNetworkReporter.h" -#include +#include #include #include diff --git a/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp index 722578f55143..eafa48563cfd 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp @@ -11,7 +11,7 @@ #include "Base64.h" #include "Utf8.h" -#include +#include #include #include @@ -272,19 +272,19 @@ bool NetworkIOAgent::handleRequest( } if (InspectorFlags::getInstance().getNetworkInspectionEnabled()) { - auto& networkReporter = NetworkReporter::getInstance(); + auto& networkHandler = NetworkHandler::getInstance(); // @cdp Network.enable support is experimental. if (req.method == "Network.enable") { - networkReporter.setFrontendChannel(frontendChannel_); - networkReporter.enableDebugging(); + networkHandler.setFrontendChannel(frontendChannel_); + networkHandler.enable(); frontendChannel_(cdp::jsonResult(req.id)); return true; } // @cdp Network.disable support is experimental. if (req.method == "Network.disable") { - networkReporter.disableDebugging(); + networkHandler.disable(); frontendChannel_(cdp::jsonResult(req.id)); return true; } @@ -497,9 +497,9 @@ void NetworkIOAgent::handleGetResponseBody(const cdp::PreparsedRequest& req) { return; } - auto& networkReporter = NetworkReporter::getInstance(); + auto& networkHandler = NetworkHandler::getInstance(); - if (!networkReporter.isDebuggingEnabled()) { + if (!networkHandler.isEnabled()) { frontendChannel_(cdp::jsonError( requestId, cdp::ErrorCode::InvalidRequest, @@ -508,7 +508,7 @@ void NetworkIOAgent::handleGetResponseBody(const cdp::PreparsedRequest& req) { } auto storedResponse = - networkReporter.getResponseBody(req.params.at("requestId").asString()); + networkHandler.getResponseBody(req.params.at("requestId").asString()); if (!storedResponse) { frontendChannel_(cdp::jsonError( diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt b/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt index 9c9aef21efac..c5d2af857bf8 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt @@ -24,6 +24,4 @@ target_include_directories(jsinspector_network PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(jsinspector_network folly_runtime glog - jsinspector_cdp - react_performance_timeline - react_timing) + jsinspector_cdp) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp index 3ca9d5c3f695..19ed4380eeb5 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp @@ -27,15 +27,6 @@ folly::dynamic headersToDynamic(const std::optional& headers) { } // namespace -/* static */ Request Request::fromInputParams(const RequestInfo& requestInfo) { - return { - .url = requestInfo.url, - .method = requestInfo.httpMethod, - .headers = requestInfo.headers, - .postData = requestInfo.httpBody, - }; -} - folly::dynamic Request::toDynamic() const { folly::dynamic result = folly::dynamic::object; @@ -48,16 +39,16 @@ folly::dynamic Request::toDynamic() const { } /* static */ Response Response::fromInputParams( - const ResponseInfo& responseInfo, + const std::string& url, + uint16_t status, + const std::optional& headers, int encodedDataLength) { - auto headers = responseInfo.headers.value_or(Headers()); - return { - .url = responseInfo.url, - .status = responseInfo.statusCode, - .statusText = httpReasonPhrase(responseInfo.statusCode), + .url = url, + .status = status, + .statusText = httpReasonPhrase(status), .headers = headers, - .mimeType = mimeTypeFromHeaders(headers), + .mimeType = mimeTypeFromHeaders(headers.value_or(Headers())), .encodedDataLength = encodedDataLength, }; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h index fa49cb9f18b1..b22c29799127 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h @@ -7,8 +7,6 @@ #pragma once -#include "NetworkTypes.h" - #include #include @@ -18,6 +16,8 @@ namespace facebook::react::jsinspector_modern::cdp::network { +using Headers = std::map; + /** * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Request */ @@ -27,12 +27,6 @@ struct Request { std::optional headers; std::optional postData; - /** - * Convenience function to construct a `Request` from the generic - * `RequestInfo` input object. - */ - static Request fromInputParams(const RequestInfo& requestInfo); - folly::dynamic toDynamic() const; }; @@ -52,7 +46,9 @@ struct Response { * `ResponseInfo` input object. */ static Response fromInputParams( - const ResponseInfo& responseInfo, + const std::string& url, + uint16_t status, + const std::optional& headers, int encodedDataLength); folly::dynamic toDynamic() const; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.cpp index 6dfb205d5baa..98fd13a051f2 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.cpp @@ -146,7 +146,8 @@ std::string httpReasonPhrase(uint16_t status) { return ""; } -std::string mimeTypeFromHeaders(const Headers& headers) { +std::string mimeTypeFromHeaders( + const std::map& headers) { std::string mimeType = "application/octet-stream"; for (const auto& [name, value] : headers) { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.h b/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.h index 9f549c2a2867..5fcd275d12c1 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/HttpUtils.h @@ -7,8 +7,7 @@ #pragma once -#include "NetworkTypes.h" - +#include #include namespace facebook::react::jsinspector_modern { @@ -22,6 +21,7 @@ std::string httpReasonPhrase(uint16_t status); * Get the MIME type for a response based on the 'Content-Type' header. If * the header is not present, returns 'application/octet-stream'. */ -std::string mimeTypeFromHeaders(const Headers& headers); +std::string mimeTypeFromHeaders( + const std::map& headers); } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp new file mode 100644 index 000000000000..80ed5bb23b19 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "NetworkHandler.h" + +#include + +#include +#include + +namespace facebook::react::jsinspector_modern { + +namespace { + +/** + * Get the current Unix timestamp in seconds (µs precision, CDP format). + */ +double getCurrentUnixTimestampSeconds() { + auto now = std::chrono::system_clock::now().time_since_epoch(); + auto seconds = std::chrono::duration_cast(now).count(); + auto micros = + std::chrono::duration_cast(now).count() % + 1000000; + + return static_cast(seconds) + + (static_cast(micros) / 1000000.0); +} + +} // namespace + +NetworkHandler& NetworkHandler::getInstance() { + static NetworkHandler instance; + return instance; +} + +void NetworkHandler::setFrontendChannel(FrontendChannel frontendChannel) { + frontendChannel_ = std::move(frontendChannel); +} + +bool NetworkHandler::enable() { + if (enabled_.load(std::memory_order_acquire)) { + return false; + } + + enabled_.store(true, std::memory_order_release); + return true; +} + +bool NetworkHandler::disable() { + if (!enabled_.load(std::memory_order_acquire)) { + return false; + } + + enabled_.store(false, std::memory_order_release); + requestBodyBuffer_.clear(); + return true; +} + +void NetworkHandler::onRequestWillBeSent( + const std::string& requestId, + const cdp::network::Request& request, + const std::optional& redirectResponse) { + if (!isEnabledNoSync()) { + return; + } + + double timestamp = getCurrentUnixTimestampSeconds(); + auto params = cdp::network::RequestWillBeSentParams{ + .requestId = requestId, + .loaderId = "", + .documentURL = "mobile", + .request = request, + // NOTE: Both timestamp and wallTime use the same unit, however wallTime + // is relative to an "arbitrary epoch". In our implementation, use the + // Unix epoch for both. + .timestamp = timestamp, + .wallTime = timestamp, + .initiator = folly::dynamic::object("type", "script"), + .redirectHasExtraInfo = redirectResponse.has_value(), + .redirectResponse = redirectResponse, + }; + + frontendChannel_( + cdp::jsonNotification("Network.requestWillBeSent", params.toDynamic())); +} + +void NetworkHandler::onRequestWillBeSentExtraInfo( + const std::string& requestId, + const Headers& headers) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::RequestWillBeSentExtraInfoParams{ + .requestId = requestId, + .headers = headers, + .connectTiming = {.requestTime = getCurrentUnixTimestampSeconds()}, + }; + + frontendChannel_(cdp::jsonNotification( + "Network.requestWillBeSentExtraInfo", params.toDynamic())); +} + +void NetworkHandler::onResponseReceived( + const std::string& requestId, + const cdp::network::Response& response) { + if (!isEnabledNoSync()) { + return; + } + + auto resourceType = cdp::network::resourceTypeFromMimeType(response.mimeType); + resourceTypeMap_.emplace(requestId, resourceType); + + auto params = cdp::network::ResponseReceivedParams{ + .requestId = requestId, + .loaderId = "", + .timestamp = getCurrentUnixTimestampSeconds(), + .type = resourceType, + .response = response, + .hasExtraInfo = false, + }; + + frontendChannel_( + cdp::jsonNotification("Network.responseReceived", params.toDynamic())); +} + +void NetworkHandler::onDataReceived( + const std::string& requestId, + int dataLength, + int encodedDataLength) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::DataReceivedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .dataLength = dataLength, + .encodedDataLength = encodedDataLength, + }; + + frontendChannel_( + cdp::jsonNotification("Network.dataReceived", params.toDynamic())); +} + +void NetworkHandler::onLoadingFinished( + const std::string& requestId, + int encodedDataLength) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::LoadingFinishedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .encodedDataLength = encodedDataLength, + }; + + frontendChannel_( + cdp::jsonNotification("Network.loadingFinished", params.toDynamic())); +} + +void NetworkHandler::onLoadingFailed( + const std::string& requestId, + bool cancelled) const { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::LoadingFailedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .type = resourceTypeMap_.find(requestId) != resourceTypeMap_.end() + ? resourceTypeMap_.at(requestId) + : "Other", + .errorText = cancelled ? "net::ERR_ABORTED" : "net::ERR_FAILED", + .canceled = cancelled, + }; + + frontendChannel_( + cdp::jsonNotification("Network.loadingFailed", params.toDynamic())); +} + +void NetworkHandler::storeResponseBody( + const std::string& requestId, + std::string_view body, + bool base64Encoded) { + std::lock_guard lock(requestBodyMutex_); + requestBodyBuffer_.put(requestId, body, base64Encoded); +} + +std::optional> NetworkHandler::getResponseBody( + const std::string& requestId) { + std::lock_guard lock(requestBodyMutex_); + auto responseBody = requestBodyBuffer_.get(requestId); + + if (responseBody == nullptr) { + return std::nullopt; + } + + return std::make_optional>( + responseBody->data, responseBody->base64Encoded); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h new file mode 100644 index 000000000000..cb38d1b76280 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "BoundedRequestBuffer.h" +#include "CdpNetwork.h" + +#include + +#include +#include +#include +#include + +namespace facebook::react::jsinspector_modern { + +/** + * A callback that can be used to send debugger messages (method responses and + * events) to the frontend. The message must be a JSON-encoded string. + * The callback may be called from any thread. + */ +using FrontendChannel = std::function; + +using Headers = std::map; + +/** + * [Experimental] Handler for reporting network events via CDP. + */ +class NetworkHandler { + public: + static NetworkHandler& getInstance(); + + /** + * Set the channel used to send CDP events to the frontend. This should be + * supplied before calling `enable`. + */ + void setFrontendChannel(FrontendChannel frontendChannel); + + /** + * Enable network debugging. Returns `false` if already enabled. + * + * @cdp Network.enable + */ + bool enable(); + + /** + * Disable network debugging. Returns `false` if not initially enabled. + * + * @cdp Network.disable + */ + bool disable(); + + /** + * Returns whether network debugging is currently enabled. + */ + inline bool isEnabled() const { + return enabled_.load(std::memory_order_acquire); + } + + /** + * @cdp Network.requestWillBeSent + */ + void onRequestWillBeSent( + const std::string& requestId, + const cdp::network::Request& request, + const std::optional& redirectResponse); + + /** + * @cdp Network.requestWillBeSentExtraInfo + */ + void onRequestWillBeSentExtraInfo( + const std::string& requestId, + const Headers& headers); + + /** + * @cdp Network.responseReceived + */ + void onResponseReceived( + const std::string& requestId, + const cdp::network::Response& response); + + /** + * @cdp Network.dataReceived + */ + void onDataReceived( + const std::string& requestId, + int dataLength, + int encodedDataLength); + + /** + * @cdp Network.loadingFinished + */ + void onLoadingFinished(const std::string& requestId, int encodedDataLength); + + /** + * @cdp Network.loadingFailed + */ + void onLoadingFailed(const std::string& requestId, bool cancelled) const; + + /** + * Store the fetched response body for a text or image network response. + * + * Reponse bodies are stored in a bounded buffer with a fixed maximum memory + * size, where oldest responses will be evicted if the buffer is exceeded. + * + * Should be called after checking \ref NetworkHandler::isEnabled. + */ + void storeResponseBody( + const std::string& requestId, + std::string_view body, + bool base64Encoded); + + /** + * Retrieve a stored response body for a given request ID. + * + * \returns An optional tuple of [responseBody, base64Encoded]. Returns + * nullopt if no entry is found in the buffer. + */ + std::optional> getResponseBody( + const std::string& requestId); + + private: + NetworkHandler() = default; + NetworkHandler(const NetworkHandler&) = delete; + NetworkHandler& operator=(const NetworkHandler&) = delete; + ~NetworkHandler() = default; + + std::atomic enabled_{false}; + + inline bool isEnabledNoSync() const { + return enabled_.load(std::memory_order_relaxed); + } + + FrontendChannel frontendChannel_; + + std::map resourceTypeMap_{}; + + BoundedRequestBuffer requestBodyBuffer_{}; + std::mutex requestBodyMutex_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp deleted file mode 100644 index f911b7e99816..000000000000 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include "NetworkReporter.h" - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED -#include "CdpNetwork.h" -#endif - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED -#include -#endif -#include -#include - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED -#include -#endif -#include -#include - -namespace facebook::react::jsinspector_modern { - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED -namespace { - -/** - * Get the current Unix timestamp in seconds (µs precision, CDP format). - */ -double getCurrentUnixTimestampSeconds() { - auto now = std::chrono::system_clock::now().time_since_epoch(); - auto seconds = std::chrono::duration_cast(now).count(); - auto micros = - std::chrono::duration_cast(now).count() % - 1000000; - - return static_cast(seconds) + - (static_cast(micros) / 1000000.0); -} - -} // namespace -#endif - -NetworkReporter& NetworkReporter::getInstance() { - static NetworkReporter instance; - return instance; -} - -void NetworkReporter::setFrontendChannel(FrontendChannel frontendChannel) { - frontendChannel_ = std::move(frontendChannel); -} - -bool NetworkReporter::enableDebugging() { - if (debuggingEnabled_.load(std::memory_order_acquire)) { - return false; - } - - debuggingEnabled_.store(true, std::memory_order_release); - return true; -} - -bool NetworkReporter::disableDebugging() { - if (!debuggingEnabled_.load(std::memory_order_acquire)) { - return false; - } - - debuggingEnabled_.store(false, std::memory_order_release); - requestBodyBuffer_.clear(); - return true; -} - -void NetworkReporter::reportRequestStart( - const std::string& requestId, - const RequestInfo& requestInfo, - int encodedDataLength, - const std::optional& redirectResponse) { - if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - auto now = HighResTimeStamp::now(); - - // All builds: Annotate PerformanceResourceTiming metadata - { - std::lock_guard lock(perfTimingsMutex_); - perfTimingsBuffer_.emplace( - requestId, - ResourceTimingData{ - .url = requestInfo.url, - .fetchStart = now, - .requestStart = now, - }); - } - } - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - double timestamp = getCurrentUnixTimestampSeconds(); - auto request = cdp::network::Request::fromInputParams(requestInfo); - auto params = cdp::network::RequestWillBeSentParams{ - .requestId = requestId, - .loaderId = "", - .documentURL = "mobile", - .request = std::move(request), - // NOTE: Both timestamp and wallTime use the same unit, however wallTime - // is relative to an "arbitrary epoch". In our implementation, use the - // Unix epoch for both. - .timestamp = timestamp, - .wallTime = timestamp, - .initiator = folly::dynamic::object("type", "script"), - .redirectHasExtraInfo = redirectResponse.has_value(), - }; - - if (redirectResponse.has_value()) { - params.redirectResponse = cdp::network::Response::fromInputParams( - redirectResponse.value(), encodedDataLength); - } - - frontendChannel_( - cdp::jsonNotification("Network.requestWillBeSent", params.toDynamic())); -#endif -} - -void NetworkReporter::reportConnectionTiming( - const std::string& requestId, - const std::optional& headers) { - if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - auto now = HighResTimeStamp::now(); - - // All builds: Annotate PerformanceResourceTiming metadata - { - std::lock_guard lock(perfTimingsMutex_); - auto it = perfTimingsBuffer_.find(requestId); - if (it != perfTimingsBuffer_.end()) { - it->second.connectStart = now; - } - } - } - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - auto params = cdp::network::RequestWillBeSentExtraInfoParams{ - .requestId = requestId, - .headers = headers.value_or(Headers{}), - .connectTiming = {.requestTime = getCurrentUnixTimestampSeconds()}, - }; - - frontendChannel_(cdp::jsonNotification( - "Network.requestWillBeSentExtraInfo", params.toDynamic())); -#endif -} - -void NetworkReporter::reportResponseStart( - const std::string& requestId, - const ResponseInfo& responseInfo, - int encodedDataLength) { - if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - auto now = HighResTimeStamp::now(); - - // All builds: Annotate PerformanceResourceTiming metadata - { - std::lock_guard lock(perfTimingsMutex_); - auto it = perfTimingsBuffer_.find(requestId); - if (it != perfTimingsBuffer_.end()) { - it->second.connectEnd = now; - it->second.responseStart = now; - it->second.responseStatus = responseInfo.statusCode; - } - } - } - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - auto response = - cdp::network::Response::fromInputParams(responseInfo, encodedDataLength); - auto resourceType = cdp::network::resourceTypeFromMimeType(response.mimeType); - resourceTypeMap_.emplace(requestId, resourceType); - - auto params = cdp::network::ResponseReceivedParams{ - .requestId = requestId, - .loaderId = "", - .timestamp = getCurrentUnixTimestampSeconds(), - .type = resourceType, - .response = response, - .hasExtraInfo = false, - }; - - frontendChannel_( - cdp::jsonNotification("Network.responseReceived", params.toDynamic())); -#endif -} - -void NetworkReporter::reportDataReceived( - const std::string& requestId, - int dataLength, - const std::optional& encodedDataLength) { -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - auto params = cdp::network::DataReceivedParams{ - .requestId = requestId, - .timestamp = getCurrentUnixTimestampSeconds(), - .dataLength = dataLength, - .encodedDataLength = encodedDataLength.value_or(dataLength), - }; - - frontendChannel_( - cdp::jsonNotification("Network.dataReceived", params.toDynamic())); -#endif -} - -void NetworkReporter::reportResponseEnd( - const std::string& requestId, - int encodedDataLength) { - if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - auto now = HighResTimeStamp::now(); - - // All builds: Report PerformanceResourceTiming event - { - std::lock_guard lock(perfTimingsMutex_); - auto it = perfTimingsBuffer_.find(requestId); - if (it != perfTimingsBuffer_.end()) { - auto& eventData = it->second; - PerformanceEntryReporter::getInstance()->reportResourceTiming( - eventData.url, - eventData.fetchStart, - eventData.requestStart, - eventData.connectStart.value_or(now), - eventData.connectEnd.value_or(now), - eventData.responseStart.value_or(now), - now, - eventData.responseStatus); - perfTimingsBuffer_.erase(requestId); - } - } - } - -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - auto params = cdp::network::LoadingFinishedParams{ - .requestId = requestId, - .timestamp = getCurrentUnixTimestampSeconds(), - .encodedDataLength = encodedDataLength, - }; - - frontendChannel_( - cdp::jsonNotification("Network.loadingFinished", params.toDynamic())); -#endif -} - -void NetworkReporter::reportRequestFailed( - const std::string& requestId, - bool cancelled) const { -#ifdef REACT_NATIVE_DEBUGGER_ENABLED - // Debug build: CDP event handling - if (!isDebuggingEnabledNoSync()) { - return; - } - - auto params = cdp::network::LoadingFailedParams{ - .requestId = requestId, - .timestamp = getCurrentUnixTimestampSeconds(), - .type = resourceTypeMap_.find(requestId) != resourceTypeMap_.end() - ? resourceTypeMap_.at(requestId) - : "Other", - .errorText = cancelled ? "net::ERR_ABORTED" : "net::ERR_FAILED", - .canceled = cancelled, - }; - - frontendChannel_( - cdp::jsonNotification("Network.loadingFailed", params.toDynamic())); -#endif -} - -void NetworkReporter::storeResponseBody( - const std::string& requestId, - std::string_view body, - bool base64Encoded) { - std::lock_guard lock(requestBodyMutex_); - requestBodyBuffer_.put(requestId, body, base64Encoded); -} - -std::optional> NetworkReporter::getResponseBody( - const std::string& requestId) { - std::lock_guard lock(requestBodyMutex_); - auto responseBody = requestBodyBuffer_.get(requestId); - - if (responseBody == nullptr) { - return std::nullopt; - } - - return std::make_optional>( - responseBody->data, responseBody->base64Encoded); -} - -} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec index d17a80a0b00e..c3cc232eb71b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec @@ -44,9 +44,6 @@ Pod::Spec.new do |s| resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: module_name) add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp') - add_dependency(s, "React-featureflags") - s.dependency "React-performancetimeline" - s.dependency "React-timing" add_rn_third_party_dependencies(s) add_rncore_dependency(s) diff --git a/packages/react-native/ReactCommon/react/networking/CMakeLists.txt b/packages/react-native/ReactCommon/react/networking/CMakeLists.txt new file mode 100644 index 000000000000..eee155065bbd --- /dev/null +++ b/packages/react-native/ReactCommon/react/networking/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + +file(GLOB react_networking_SRC CONFIGURE_DEPENDS *.cpp) +add_library(react_networking OBJECT ${react_networking_SRC}) + +target_compile_reactnative_options(react_networking PRIVATE) +target_compile_options(react_networking PRIVATE -Wpedantic) + +target_include_directories(react_networking PUBLIC ${REACT_COMMON_DIR}) +target_link_libraries(react_networking + folly_runtime + react_performance_timeline + react_timing) diff --git a/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp b/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp new file mode 100644 index 000000000000..7ea6572f5b0b --- /dev/null +++ b/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "NetworkReporter.h" + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED +#include "jsinspector-modern/network/NetworkHandler.h" +#endif +#include +#include + +namespace facebook::react { + +NetworkReporter& NetworkReporter::getInstance() { + static NetworkReporter instance; + return instance; +} + +bool NetworkReporter::isDebuggingEnabled() const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + return jsinspector_modern::NetworkHandler::getInstance().isEnabled(); +#else + return false; +#endif +} + +void NetworkReporter::reportRequestStart( + const std::string& requestId, + const RequestInfo& requestInfo, + int encodedDataLength, + const std::optional& redirectResponse) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = HighResTimeStamp::now(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + perfTimingsBuffer_.emplace( + requestId, + ResourceTimingData{ + .url = requestInfo.url, + .fetchStart = now, + .requestStart = now, + }); + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onRequestWillBeSent( + requestId, + { + .url = requestInfo.url, + .method = requestInfo.httpMethod, + .headers = requestInfo.headers, + .postData = requestInfo.httpBody, + }, + redirectResponse.has_value() + ? std::optional( + jsinspector_modern::cdp::network::Response::fromInputParams( + redirectResponse->url, + redirectResponse->statusCode, + redirectResponse->headers, + encodedDataLength)) + : std::nullopt); +#endif +} + +void NetworkReporter::reportConnectionTiming( + const std::string& requestId, + const std::optional& headers) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = HighResTimeStamp::now(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.connectStart = now; + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + jsinspector_modern::NetworkHandler::getInstance() + .onRequestWillBeSentExtraInfo(requestId, headers.value_or(Headers{})); +#endif +} + +void NetworkReporter::reportResponseStart( + const std::string& requestId, + const ResponseInfo& responseInfo, + int encodedDataLength) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = HighResTimeStamp::now(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.connectEnd = now; + it->second.responseStart = now; + it->second.responseStatus = responseInfo.statusCode; + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onResponseReceived( + requestId, + jsinspector_modern::cdp::network::Response::fromInputParams( + responseInfo.url, + responseInfo.statusCode, + responseInfo.headers, + encodedDataLength)); +#endif +} + +void NetworkReporter::reportDataReceived( + const std::string& requestId, + int dataLength, + const std::optional& encodedDataLength) { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onDataReceived( + requestId, dataLength, encodedDataLength.value_or(dataLength)); +#endif +} + +void NetworkReporter::reportResponseEnd( + const std::string& requestId, + int encodedDataLength) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = HighResTimeStamp::now(); + + // All builds: Report PerformanceResourceTiming event + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + auto& eventData = it->second; + PerformanceEntryReporter::getInstance()->reportResourceTiming( + eventData.url, + eventData.fetchStart, + eventData.requestStart, + eventData.connectStart.value_or(now), + eventData.connectEnd.value_or(now), + eventData.responseStart.value_or(now), + now, + eventData.responseStatus); + perfTimingsBuffer_.erase(requestId); + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onLoadingFinished( + requestId, encodedDataLength); +#endif +} + +void NetworkReporter::reportRequestFailed( + const std::string& requestId, + bool cancelled) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onLoadingFailed( + requestId, cancelled); +#endif +} + +void NetworkReporter::storeResponseBody( + const std::string& requestId, + std::string_view body, + bool base64Encoded) { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: Store fetched response body for later CDP retrieval + jsinspector_modern::NetworkHandler::getInstance().storeResponseBody( + requestId, body, base64Encoded); +#endif +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h b/packages/react-native/ReactCommon/react/networking/NetworkReporter.h similarity index 70% rename from packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h rename to packages/react-native/ReactCommon/react/networking/NetworkReporter.h index 69c0972b7519..3e5c2c227ab4 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h +++ b/packages/react-native/ReactCommon/react/networking/NetworkReporter.h @@ -7,27 +7,16 @@ #pragma once -#include "BoundedRequestBuffer.h" #include "NetworkTypes.h" #include #include -#include -#include #include #include -#include #include -namespace facebook::react::jsinspector_modern { - -/** - * A callback that can be used to send debugger messages (method responses and - * events) to the frontend. The message must be a JSON-encoded string. - * The callback may be called from any thread. - */ -using FrontendChannel = std::function; +namespace facebook::react { /** * Container for static network event metadata aligning with the @@ -57,34 +46,10 @@ class NetworkReporter { public: static NetworkReporter& getInstance(); - /** - * Set the channel used to send CDP events to the frontend. This should be - * supplied before calling `enableDebugging`. - */ - void setFrontendChannel(FrontendChannel frontendChannel); - - /** - * Enable network tracking over CDP. Once enabled, network events will be - * sent to the debugger client. Returns `false` if already enabled. - * - * Corresponds to `Network.enable` in CDP. - */ - bool enableDebugging(); - - /** - * Disable network tracking over CDP, preventing network events from being - * sent to the debugger client. Returns `false` if not initially enabled. - * - * Corresponds to `Network.disable` in CDP. - */ - bool disableDebugging(); - /** * Returns whether network tracking over CDP is currently enabled. */ - inline bool isDebuggingEnabled() const { - return debuggingEnabled_.load(std::memory_order_acquire); - } + bool isDebuggingEnabled() const; /** * Report a network request that is about to be sent. @@ -174,38 +139,14 @@ class NetworkReporter { std::string_view body, bool base64Encoded); - /** - * Retrieve a stored response body for a given request ID. - * - * \returns An optional tuple of [responseBody, base64Encoded]. Returns - * nullopt if no entry is found in the buffer. - */ - std::optional> getResponseBody( - const std::string& requestId); - private: NetworkReporter() = default; NetworkReporter(const NetworkReporter&) = delete; NetworkReporter& operator=(const NetworkReporter&) = delete; ~NetworkReporter() = default; - std::atomic debuggingEnabled_{false}; - - inline bool isDebuggingEnabledNoSync() const { - return debuggingEnabled_.load(std::memory_order_relaxed); - } - - FrontendChannel frontendChannel_; - std::unordered_map perfTimingsBuffer_{}; std::mutex perfTimingsMutex_; - - // Only populated when CDP debugging is enabled. - std::map resourceTypeMap_{}; - - // Only populated when CDP debugging is enabled. - BoundedRequestBuffer requestBodyBuffer_{}; - std::mutex requestBodyMutex_; }; -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkTypes.h b/packages/react-native/ReactCommon/react/networking/NetworkTypes.h similarity index 88% rename from packages/react-native/ReactCommon/jsinspector-modern/network/NetworkTypes.h rename to packages/react-native/ReactCommon/react/networking/NetworkTypes.h index f493326a0631..d79256d017b0 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkTypes.h +++ b/packages/react-native/ReactCommon/react/networking/NetworkTypes.h @@ -13,7 +13,7 @@ // Defines generic input object types for NetworkReporter. -namespace facebook::react::jsinspector_modern { +namespace facebook::react { /** * A collection of parsed HTTP headers. @@ -39,4 +39,4 @@ struct ResponseInfo { std::optional headers; }; -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/networking/React-networking.podspec b/packages/react-native/ReactCommon/react/networking/React-networking.podspec new file mode 100644 index 000000000000..233bdf981ee9 --- /dev/null +++ b/packages/react-native/ReactCommon/react/networking/React-networking.podspec @@ -0,0 +1,51 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +header_search_paths = [] + +if ENV['USE_FRAMEWORKS'] + header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../..\"" # this is needed to allow the feature flags access its own files +end + +Pod::Spec.new do |s| + s.name = "React-networking" + s.version = version + s.summary = "Common networking modules for React Native" + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = podspec_sources("*.{cpp,h}", "*.h") + s.header_dir = "react/networking" + s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), + "DEFINES_MODULE" => "YES" } + + if ENV['USE_FRAMEWORKS'] && ReactNativeCoreUtils.build_rncore_from_source() + s.module_name = "React_networking" + s.header_mappings_dir = "../.." + end + add_dependency(s, "React-featureflags") + add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork') + s.dependency "React-performancetimeline" + s.dependency "React-timing" + + add_rn_third_party_dependencies(s) + add_rncore_dependency(s) +end diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index fbf64da3e57e..7dff2b950633 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -162,6 +162,7 @@ def use_react_native! ( pod 'React-jsinspectortracing', :path => "#{prefix}/ReactCommon/jsinspector-modern/tracing" pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker" + pod 'React-networking', :path => "#{prefix}/ReactCommon/react/networking" pod 'React-performancecdpmetrics', :path => "#{prefix}/ReactCommon/react/performance/cdpmetrics" pod 'React-performancetimeline', :path => "#{prefix}/ReactCommon/react/performance/timeline" pod 'React-timing', :path => "#{prefix}/ReactCommon/react/timing" From 968909488a844c695a92ce000497840e577190dd Mon Sep 17 00:00:00 2001 From: 25harsh Date: Tue, 9 Sep 2025 02:23:52 -0700 Subject: [PATCH 0077/1110] fix(iOS): Fix RCTDeviceInfo crash when application.delegate.window is nil (#53645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Fixes a crash in `RCTDeviceInfo.interfaceOrientationDidChange` when `application.delegate.window` is nil. This crash affects multiple modern iOS app architectures where the traditional window property may not be set: - **SwiftUI apps using `main`** instead of traditional AppDelegate - **Brownfield React Native integrations** where the host app manages windows - **Scene-based lifecycle apps** (iOS 13+) using SceneDelegate - **Custom window management** setups **The Problem:** ``` *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyApp.AppDelegate window]: unrecognized selector sent to instance' ``` This occurs when trying to access `.frame` on a nil window object during orientation changes. Modern iOS development patterns don't always require setting `application.delegate.window`, but React Native's RCTDeviceInfo assumes this property exists. **The Solution:** Replace direct `application.delegate.window` access with `RCTKeyWindow()` and add nil-safe fallback: ```objc // Before (crashes in modern apps) BOOL isRunningInFullScreen = CGRectEqualToRect(application.delegate.window.frame, application.delegate.window.screen.bounds); // After (safe for all app configurations) UIWindow *delegateWindow = RCTKeyWindow(); BOOL isRunningInFullScreen = delegateWindow ? CGRectEqualToRect(delegateWindow.frame, delegateWindow.screen.bounds) : YES; ``` This approach: - Uses `RCTKeyWindow()` pattern already established elsewhere in RCTDeviceInfo - Provides safe fallback defaulting to fullscreen when window state is unknown - Maintains existing multitasking detection behavior (Split View, Slide Over) - Is backward compatible with traditional React Native apps ## Changelog: [IOS][FIXED] - Fix RCTDeviceInfo crash when application.delegate.window is nil in modern iOS app architectures Pull Request resolved: https://github.com/facebook/react-native/pull/53645 Test Plan: ### Manual Testing **1. SwiftUI main App Test:** ```bash # Created SwiftUI app with main lifecycle # Integrated React Native component # Result: No crash during orientation changes, fullscreen detection works ✅ PASS: Orientation changes handled safely ✅ PASS: Multitasking detection stable ``` **2. Traditional React Native App:** ```bash # Tested with standard RN template app # Verified existing behavior unchanged ✅ PASS: Existing functionality preserved ✅ PASS: No regressions in dimension reporting ``` **3. Brownfield Integration:** ```bash # Integrated RN in existing iOS app without window property # Triggered orientation changes and multitasking transitions ✅ PASS: No crashes during orientation events ✅ PASS: Split View and Slide Over work correctly ``` **4. Scene-based Lifecycle App:** ```bash # Created app using SceneDelegate for window management # Tested orientation and multitasking scenarios ✅ PASS: Proper handling when SceneDelegate manages windows ✅ PASS: No crashes during app lifecycle transitions ``` ### Edge Case Testing **RCTKeyWindow() Returns Nil:** - Confirmed defaults to `YES` (fullscreen) - No crashes when no key window available - Multitasking detection remains stable **Multiple Window Scenarios:** - Tested with iPad multiple windows - Uses correct key window for measurements - Proper behavior in complex window hierarchies **Orientation During Transitions:** - App backgrounding/foregrounding during orientation - Multitasking mode changes during rotation - No crashes or inconsistent states ### Automated Testing ```bash # All existing tests pass yarn test ✅ RCTDeviceInfoTests pass # Code style compliance yarn lint ✅ Follows React Native Objective-C guidelines ``` ### Impact Verification **Before Fix:** - Crash in SwiftUI apps using main - Crash in Scene-based lifecycle apps - Crash in brownfield integrations **After Fix:** - All app architectures work safely - Multitasking detection preserved - Backward compatibility maintained - No performance impact Rollback Plan: Reviewed By: javache Differential Revision: D81931754 Pulled By: cipolleschi fbshipit-source-id: c3ea1a2922b1d48ca6bc1fc32861b490322fd254 --- packages/react-native/React/CoreModules/RCTDeviceInfo.mm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index e1efd2f73425..81a25fc26985 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -238,11 +238,10 @@ - (void)didReceiveNewContentSizeMultiplier - (void)interfaceOrientationDidChange { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - UIApplication *application = RCTSharedApplication(); - UIInterfaceOrientation nextOrientation = RCTKeyWindow().windowScene.interfaceOrientation; + UIWindow *window = RCTKeyWindow(); + UIInterfaceOrientation nextOrientation = window.windowScene.interfaceOrientation; - BOOL isRunningInFullScreen = - CGRectEqualToRect(application.delegate.window.frame, application.delegate.window.screen.bounds); + BOOL isRunningInFullScreen = window ? CGRectEqualToRect(window.frame, window.screen.bounds) : YES; // We are catching here two situations for multitasking view: // a) The app is in Split View and the container gets resized -> !isRunningInFullScreen // b) The app changes to/from fullscreen example: App runs in slide over mode and goes into fullscreen-> From f568c9b953c9bf2c27817ffb21db6303d918d5ce Mon Sep 17 00:00:00 2001 From: generatedunixname537391475639613 Date: Tue, 9 Sep 2025 03:42:58 -0700 Subject: [PATCH 0078/1110] xplat/js/react-native-github/packages/gradle-plugin/shared-testutil/src/main/kotlin/com/facebook/react/tests/OsRule.kt (#53655) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53655 Reviewed By: cortinico Differential Revision: D82008708 fbshipit-source-id: 9ba77512a5e6e7749981726d739d88df287a63d3 --- .../src/main/kotlin/com/facebook/react/tests/OsRule.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/gradle-plugin/shared-testutil/src/main/kotlin/com/facebook/react/tests/OsRule.kt b/packages/gradle-plugin/shared-testutil/src/main/kotlin/com/facebook/react/tests/OsRule.kt index 8d500d0a7192..73aecea5f9ee 100644 --- a/packages/gradle-plugin/shared-testutil/src/main/kotlin/com/facebook/react/tests/OsRule.kt +++ b/packages/gradle-plugin/shared-testutil/src/main/kotlin/com/facebook/react/tests/OsRule.kt @@ -25,14 +25,14 @@ class OsRule : TestRule { override fun evaluate() { val annotation = description.annotations.filterIsInstance().firstOrNull() - annotation?.os?.propertyName?.let { + annotation?.os?.propertyName?.let { osName -> retainOs = System.getProperty(OS_NAME_KEY) - System.setProperty(OS_NAME_KEY, it) + System.setProperty(OS_NAME_KEY, osName) } - annotation?.arch?.let { - if (it.isNotBlank()) { + annotation?.arch?.let { arch -> + if (arch.isNotBlank()) { retainArch = System.getProperty(OS_ARCH_KEY) - System.setProperty(OS_ARCH_KEY, it) + System.setProperty(OS_ARCH_KEY, arch) } } try { From 2c30215bc77ff8b1d675e143ca5a2633f971c80c Mon Sep 17 00:00:00 2001 From: generatedunixname537391475639613 Date: Tue, 9 Sep 2025 04:57:44 -0700 Subject: [PATCH 0079/1110] xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/core/tests/TestComponent.h (#53658) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53658 Reviewed By: javache Differential Revision: D81904578 fbshipit-source-id: 03725446586cc206690995f7d2d274c0b1249abe --- .../ReactCommon/react/renderer/core/tests/TestComponent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/TestComponent.h b/packages/react-native/ReactCommon/react/renderer/core/tests/TestComponent.h index f98f93c884b2..4ba4ee25344d 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/TestComponent.h +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/TestComponent.h @@ -37,6 +37,7 @@ struct TestState { #endif }; +// NOLINTNEXTLINE(modernize-avoid-c-arrays) static const char TestComponentName[] = "Test"; class TestProps : public ViewProps { From 59b8974d3c88c56730a4c1c4cdbd5e22bc422e77 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Tue, 9 Sep 2025 07:46:35 -0700 Subject: [PATCH 0080/1110] changelog/v0.80.0-rc.1 (#53662) Summary: Changelog: [Internal] Pull Request resolved: https://github.com/facebook/react-native/pull/53662 Reviewed By: fabriziocucci Differential Revision: D82020526 Pulled By: vzaidman fbshipit-source-id: 43eb1d038f71dbeb30bd1bf4a94779a9fad56215 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bada6155afa6..402b8a53363c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## v0.82.0-rc.1 + +### Added + +#### Android specific + +- **Hermes V1:** Added opt-in to use the new Hermes ([3e9990f860](https://github.com/facebook/react-native/commit/3e9990f860eb9380837ef431ca02def32c4261ad) by [@j-piasecki](https://github.com/j-piasecki)) + +#### iOS specific + +- **Hermes V1:** Added opt-in to use the new Hermes ([e9cdc308b4](https://github.com/facebook/react-native/commit/e9cdc308b4c04753d85757e8877ac00c3c687b95) by [@j-piasecki](https://github.com/j-piasecki)) + +### Changed + +- **Hermes V1:** Changed the source of hermesc binary to be an npm package ([2e0bd13a25](https://github.com/facebook/react-native/commit/2e0bd13a2533fe7ab64125a95b9215b806018c6e) by [@j-piasecki](https://github.com/j-piasecki)) + +### Deprecated + +- **APIs:** Deprecate legacy javascript react native apis ([e7aeea26bd](https://github.com/facebook/react-native/commit/e7aeea26bde6e9cda0a3a0a55fc2a0421fb0c0e5) by [@RSNara](https://github.com/RSNara)) + +### Fixed + +#### Android specific + +- **Build From Source:** Fix build from source due to missing folder error on Gradle 9.0 ([9fbce3eff1](https://github.com/facebook/react-native/commit/9fbce3eff18060f16e796badc415ba733ede19af) by [@cortinico](https://github.com/cortinico)) + +#### iOS specific + +- **RCTAlertController:** Simplify RCTAlertController, don't create additional UIWindow ([05c4321b19](https://github.com/facebook/react-native/commit/05c4321b194c3d0e146b6085bcaccc75acd3fd67) by [@okwasniewski](https://github.com/okwasniewski)) +- **Prebuild:** Fix Node scripts related to prebuilt tarball extraction for paths containing whitespaces ([9731e8ebc5](https://github.com/facebook/react-native/commit/9731e8ebc5ea87526a91b9903172639e062cd920) by [@kitten](https://github.com/kitten)) +- **Prebuild:** Use autolinking-generated react-native-config output in second step of cocoapods linking that generates artifacts and generated source ([f170db412b](https://github.com/facebook/react-native/commit/f170db412b3ab46fd0894d5d66431d9c230cd3a8) by [@kitten](https://github.com/kitten)) + ## v0.82.0-rc.0 ### Breaking From a16c6c9477506e298008ec008b7e5d3cc26212b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 9 Sep 2025 08:07:31 -0700 Subject: [PATCH 0081/1110] Add feature flag to enable Web Performance APIs by default (#53547) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53547 Changelog: [internal] This creates a new feature flag to enable the modern Web performance APIs in RN by default. It's disabled by default so it shouldn't have any effect at the moment. Reviewed By: rshest Differential Revision: D80811430 fbshipit-source-id: 47d5fd12ac8809aa3c5ad37cdd31c0d9e3ed5912 --- packages/react-native/Package.swift | 9 + .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../ReactAndroid/src/main/jni/CMakeLists.txt | 3 + .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 70 +++-- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../nativemodule/defaults/CMakeLists.txt | 1 + .../defaults/DefaultTurboModules.cpp | 8 + .../React-defaultsnativemodule.podspec | 1 + .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../webperformance/CMakeLists.txt | 2 +- .../React-webperformancenativemodule.podspec | 58 ++++ .../ReactNativeFeatureFlags.config.js | 10 + .../react-native/scripts/react_native_pods.rb | 1 + .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- .../Performance/PerformanceApiExample.js | 270 ++++++++++++++++++ .../js/utils/RNTesterList.android.js | 10 + .../rn-tester/js/utils/RNTesterList.ios.js | 10 + 31 files changed, 533 insertions(+), 46 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancenativemodule.podspec create mode 100644 packages/rn-tester/js/examples/Performance/PerformanceApiExample.js diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index 4617284315d0..f4f5eb815c66 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -316,6 +316,13 @@ let reactIdleCallbacksNativeModule = RNTarget( dependencies: [.reactNativeDependencies, .reactDebug, .reactFeatureFlags, .reactUtils, .reactPerfLogger, .reactCxxReact, .reactTurboModuleCore] ) +/// React-webperformance.podspec +let reactWebPerformanceNativeModule = RNTarget( + name: .reactWebPerformanceNativeModule, + path: "ReactCommon/react/nativemodule/webperformance", + dependencies: [.reactNativeDependencies, .reactCxxReact, .reactTurboModuleCore, .reactPerformanceTimeline] +) + /// React-featureflagnativemodule.podspec let reactFeatureflagsNativemodule = RNTarget( name: .reactFeatureflagsNativemodule, @@ -603,6 +610,7 @@ let targets = [ reactTurboModuleCoreDefaults, reactTurboModuleCoreMicrotasks, reactIdleCallbacksNativeModule, + reactWebPerformanceNativeModule, reactFeatureflagsNativemodule, reactNativeModuleDom, reactAppDelegate, @@ -778,6 +786,7 @@ extension String { static let reactTurboModuleCoreDefaults = "ReactCommon/turbomodule/core/defaults" static let reactTurboModuleCoreMicrotasks = "ReactCommon/turbomodule/core/microtasks" static let reactIdleCallbacksNativeModule = "React-idlecallbacksnativemodule" + static let reactWebPerformanceNativeModule = "React-webperformancenativemodule" static let reactFeatureflagsNativemodule = "React-featureflagsnativemodule" static let reactNativeModuleDom = "React-domnativemodule" static let reactAppDelegate = "React-RCTAppDelegate" diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 568f8b059e01..a017c1ce3f5c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<8300eb10fb4906468cd5f04afd8e16cd>> */ /** @@ -300,6 +300,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableVirtualViewWindowFocusDetection(): Boolean = accessor.enableVirtualViewWindowFocusDetection() + /** + * Enable Web Performance APIs (Performance Timeline, User Timings, etc.) by default. + */ + @JvmStatic + public fun enableWebPerformanceAPIsByDefault(): Boolean = accessor.enableWebPerformanceAPIsByDefault() + /** * Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index b426a1fd658d..901c433dc331 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4ed350d8dfa42caf27d346dfcfbed974>> + * @generated SignedSource<> */ /** @@ -65,6 +65,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableVirtualViewDebugFeaturesCache: Boolean? = null private var enableVirtualViewRenderStateCache: Boolean? = null private var enableVirtualViewWindowFocusDetectionCache: Boolean? = null + private var enableWebPerformanceAPIsByDefaultCache: Boolean? = null private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null private var fuseboxEnabledReleaseCache: Boolean? = null private var fuseboxNetworkInspectionEnabledCache: Boolean? = null @@ -496,6 +497,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableWebPerformanceAPIsByDefault(): Boolean { + var cached = enableWebPerformanceAPIsByDefaultCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableWebPerformanceAPIsByDefault() + enableWebPerformanceAPIsByDefaultCache = cached + } + return cached + } + override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean { var cached = fixMappingOfEventPrioritiesBetweenFabricAndReactCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index df9b8934a0ab..2b50a28d1e97 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6c201de02071a834e411b6761d7f863b>> + * @generated SignedSource<> */ /** @@ -118,6 +118,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableVirtualViewWindowFocusDetection(): Boolean + @DoNotStrip @JvmStatic public external fun enableWebPerformanceAPIsByDefault(): Boolean + @DoNotStrip @JvmStatic public external fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean @DoNotStrip @JvmStatic public external fun fuseboxEnabledRelease(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index b442387b1b22..017a092a956e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7c4c3bf6b720f3e15fb97d8f1a4d74ba>> + * @generated SignedSource<<305496047314dc97352a6e0dad0bba54>> */ /** @@ -113,6 +113,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableVirtualViewWindowFocusDetection(): Boolean = false + override fun enableWebPerformanceAPIsByDefault(): Boolean = false + override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean = false override fun fuseboxEnabledRelease(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index ba9bd8491674..7c1218297ae6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<57fca2370d6b3f1edf7045a0b4c94350>> + * @generated SignedSource<> */ /** @@ -69,6 +69,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableVirtualViewDebugFeaturesCache: Boolean? = null private var enableVirtualViewRenderStateCache: Boolean? = null private var enableVirtualViewWindowFocusDetectionCache: Boolean? = null + private var enableWebPerformanceAPIsByDefaultCache: Boolean? = null private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null private var fuseboxEnabledReleaseCache: Boolean? = null private var fuseboxNetworkInspectionEnabledCache: Boolean? = null @@ -545,6 +546,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableWebPerformanceAPIsByDefault(): Boolean { + var cached = enableWebPerformanceAPIsByDefaultCache + if (cached == null) { + cached = currentProvider.enableWebPerformanceAPIsByDefault() + accessedFeatureFlags.add("enableWebPerformanceAPIsByDefault") + enableWebPerformanceAPIsByDefaultCache = cached + } + return cached + } + override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean { var cached = fixMappingOfEventPrioritiesBetweenFabricAndReactCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 6dcce666f1f4..eb11a458e215 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -113,6 +113,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableVirtualViewWindowFocusDetection(): Boolean + @DoNotStrip public fun enableWebPerformanceAPIsByDefault(): Boolean + @DoNotStrip public fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean @DoNotStrip public fun fuseboxEnabledRelease(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index dec36bef5f2c..04676e762177 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -124,6 +124,7 @@ add_react_common_subdir(react/nativemodule/dom) add_react_common_subdir(react/nativemodule/featureflags) add_react_common_subdir(react/nativemodule/microtasks) add_react_common_subdir(react/nativemodule/idlecallbacks) +add_react_common_subdir(react/nativemodule/webperformance) add_react_common_subdir(react/networking) add_react_common_subdir(jserrorhandler) add_react_common_subdir(react/runtime) @@ -191,6 +192,7 @@ add_library(reactnative $ $ $ + $ $ $ $ @@ -281,6 +283,7 @@ target_include_directories(reactnative $ $ $ + $ $ $ $ diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 2a85b8f62b5d..d1dac50f9dc3 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<846db2d7c3c6020dec04789fe2784f8a>> + * @generated SignedSource<> */ /** @@ -309,6 +309,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableWebPerformanceAPIsByDefault() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableWebPerformanceAPIsByDefault"); + return method(javaProvider_); + } + bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("fixMappingOfEventPrioritiesBetweenFabricAndReact"); @@ -688,6 +694,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableVirtualViewWindowFocusDetection( return ReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection(); } +bool JReactNativeFeatureFlagsCxxInterop::enableWebPerformanceAPIsByDefault( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault(); +} + bool JReactNativeFeatureFlagsCxxInterop::fixMappingOfEventPrioritiesBetweenFabricAndReact( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact(); @@ -979,6 +990,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableVirtualViewWindowFocusDetection", JReactNativeFeatureFlagsCxxInterop::enableVirtualViewWindowFocusDetection), + makeNativeMethod( + "enableWebPerformanceAPIsByDefault", + JReactNativeFeatureFlagsCxxInterop::enableWebPerformanceAPIsByDefault), makeNativeMethod( "fixMappingOfEventPrioritiesBetweenFabricAndReact", JReactNativeFeatureFlagsCxxInterop::fixMappingOfEventPrioritiesBetweenFabricAndReact), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 78bd9446464a..ebd93bf2c20d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<8cd0afd7badb6739b4e8f9b0e630677c>> */ /** @@ -165,6 +165,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableVirtualViewWindowFocusDetection( facebook::jni::alias_ref); + static bool enableWebPerformanceAPIsByDefault( + facebook::jni::alias_ref); + static bool fixMappingOfEventPrioritiesBetweenFabricAndReact( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 5bde2a82dd93..a6c17b693ebd 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -206,6 +206,10 @@ bool ReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection() { return getAccessor().enableVirtualViewWindowFocusDetection(); } +bool ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault() { + return getAccessor().enableWebPerformanceAPIsByDefault(); +} + bool ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact() { return getAccessor().fixMappingOfEventPrioritiesBetweenFabricAndReact(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 0f69f8e64089..73be894c1db8 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8979be03db13b9f45d313018192c0c35>> + * @generated SignedSource<> */ /** @@ -264,6 +264,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableVirtualViewWindowFocusDetection(); + /** + * Enable Web Performance APIs (Performance Timeline, User Timings, etc.) by default. + */ + RN_EXPORT static bool enableWebPerformanceAPIsByDefault(); + /** * Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 76e56848e1f5..de95a44646d3 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<91188495ce43a1e1c820cdc3d6302bda>> */ /** @@ -839,6 +839,24 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewWindowFocusDetection() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableWebPerformanceAPIsByDefault() { + auto flagValue = enableWebPerformanceAPIsByDefault_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(45, "enableWebPerformanceAPIsByDefault"); + + flagValue = currentProvider_->enableWebPerformanceAPIsByDefault(); + enableWebPerformanceAPIsByDefault_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAndReact() { auto flagValue = fixMappingOfEventPrioritiesBetweenFabricAndReact_.load(); @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(46, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "fuseboxEnabledRelease"); + markFlagAsAccessed(47, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(48, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(49, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(50, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "perfMonitorV2Enabled"); + markFlagAsAccessed(51, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -956,7 +974,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "preparedTextCacheSize"); + markFlagAsAccessed(52, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(53, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(54, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(55, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(56, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(57, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(58, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(59, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useFabricInterop"); + markFlagAsAccessed(60, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(61, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(62, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(63, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(64, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "useRawPropsJsiValue"); + markFlagAsAccessed(65, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "useShadowNodeStateOnClone"); + markFlagAsAccessed(66, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "useTurboModuleInterop"); + markFlagAsAccessed(67, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "useTurboModules"); + markFlagAsAccessed(68, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1262,7 +1280,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "virtualViewHysteresisRatio"); + markFlagAsAccessed(69, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1280,7 +1298,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "virtualViewPrerenderRatio"); + markFlagAsAccessed(70, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 92dbd53cb37a..cab042ef77dd 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<17f7a8577d05b191010622fb58ba27f8>> */ /** @@ -77,6 +77,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableVirtualViewDebugFeatures(); bool enableVirtualViewRenderState(); bool enableVirtualViewWindowFocusDetection(); + bool enableWebPerformanceAPIsByDefault(); bool fixMappingOfEventPrioritiesBetweenFabricAndReact(); bool fuseboxEnabledRelease(); bool fuseboxNetworkInspectionEnabled(); @@ -113,7 +114,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 70> accessedFeatureFlags_; + std::array, 71> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -160,6 +161,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableVirtualViewDebugFeatures_; std::atomic> enableVirtualViewRenderState_; std::atomic> enableVirtualViewWindowFocusDetection_; + std::atomic> enableWebPerformanceAPIsByDefault_; std::atomic> fixMappingOfEventPrioritiesBetweenFabricAndReact_; std::atomic> fuseboxEnabledRelease_; std::atomic> fuseboxNetworkInspectionEnabled_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index e9f2bfbd67be..02288bceec64 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<68b40caf4c7d5d92cb5d5eb7bde74ab3>> + * @generated SignedSource<> */ /** @@ -207,6 +207,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableWebPerformanceAPIsByDefault() override { + return false; + } + bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 02c7a2d48e86..5cc4826de5b9 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<742e5536d6d174e42a71b205789f93fd>> + * @generated SignedSource<> */ /** @@ -450,6 +450,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableVirtualViewWindowFocusDetection(); } + bool enableWebPerformanceAPIsByDefault() override { + auto value = values_["enableWebPerformanceAPIsByDefault"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableWebPerformanceAPIsByDefault(); + } + bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override { auto value = values_["fixMappingOfEventPrioritiesBetweenFabricAndReact"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 284c5474e7c4..79fb200ccffb 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<40a8180c5d1ebf49c72e6f0fea4201c9>> + * @generated SignedSource<> */ /** @@ -70,6 +70,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableVirtualViewDebugFeatures() = 0; virtual bool enableVirtualViewRenderState() = 0; virtual bool enableVirtualViewWindowFocusDetection() = 0; + virtual bool enableWebPerformanceAPIsByDefault() = 0; virtual bool fixMappingOfEventPrioritiesBetweenFabricAndReact() = 0; virtual bool fuseboxEnabledRelease() = 0; virtual bool fuseboxNetworkInspectionEnabled() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt index 731b4c4930eb..fb026a09ca27 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt @@ -21,6 +21,7 @@ target_link_libraries(react_nativemodule_defaults react_nativemodule_featureflags react_nativemodule_microtasks react_nativemodule_idlecallbacks + react_nativemodule_webperformance ) target_compile_reactnative_options(react_nativemodule_defaults PRIVATE) target_compile_options(react_nativemodule_defaults PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index 479a55ebe50f..9eb531acabec 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -6,10 +6,12 @@ */ #include "DefaultTurboModules.h" +#include #include #include #include #include +#include #ifdef REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY #include @@ -36,6 +38,12 @@ namespace facebook::react { return std::make_shared(jsInvoker); } + if (ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault()) { + if (name == NativePerformance::kModuleName) { + return std::make_shared(jsInvoker); + } + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY if (name == DevToolsRuntimeSettingsModule::kModuleName) { return std::make_shared(jsInvoker); diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec index 88cfdc7f30ca..1e240daa3d7b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec @@ -50,5 +50,6 @@ Pod::Spec.new do |s| s.dependency "React-featureflagsnativemodule" s.dependency "React-microtasksnativemodule" s.dependency "React-idlecallbacksnativemodule" + s.dependency "React-webperformancenativemodule" add_dependency(s, "React-RCTFBReactNativeSpec") end diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 6423c16c52d8..4d3c046c5162 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<95f3b1a29280420bf97cdadbcf9f11da>> + * @generated SignedSource<<702c4023cfafdd477cba85c6efe94d93>> */ /** @@ -269,6 +269,11 @@ bool NativeReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection( return ReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection(); } +bool NativeReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault(); +} + bool NativeReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index ad8249bb0834..479d029bfef6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<96e93863588a9878db7c812435564603>> */ /** @@ -126,6 +126,8 @@ class NativeReactNativeFeatureFlags bool enableVirtualViewWindowFocusDetection(jsi::Runtime& runtime); + bool enableWebPerformanceAPIsByDefault(jsi::Runtime& runtime); + bool fixMappingOfEventPrioritiesBetweenFabricAndReact(jsi::Runtime& runtime); bool fuseboxEnabledRelease(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt index 7a957025baff..62994635c30b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt @@ -7,13 +7,13 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) - file(GLOB react_nativemodule_webperformance_SRC CONFIGURE_DEPENDS *.cpp) add_library(react_nativemodule_webperformance OBJECT ${react_nativemodule_webperformance_SRC}) target_include_directories(react_nativemodule_webperformance PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(react_nativemodule_webperformance + react_performance_timeline react_codegen_rncore react_cxxreact ) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancenativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancenativemodule.podspec new file mode 100644 index 000000000000..ad9589c38ab9 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancenativemodule.podspec @@ -0,0 +1,58 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +header_search_paths = [] + +if ENV['USE_FRAMEWORKS'] + header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files +end + +Pod::Spec.new do |s| + s.name = "React-webperformancenativemodule" + s.version = version + s.summary = "React Native idle callbacks native module" + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = podspec_sources("*.{cpp,h}", "*.h") + s.header_dir = "react/nativemodule/webperformance" + s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), + "OTHER_CFLAGS" => "$(inherited)", + "DEFINES_MODULE" => "YES" } + + if ENV['USE_FRAMEWORKS'] + s.module_name = "webperformancenativemodule" + s.header_mappings_dir = "../.." + end + + s.dependency "React-jsi" + s.dependency "React-jsiexecutor" + + depend_on_js_engine(s) + add_rn_third_party_dependencies(s) + add_rncore_dependency(s) + + s.dependency "ReactCommon/turbomodule/core" + add_dependency(s, "React-RCTFBReactNativeSpec") + add_dependency(s, "React-performancetimeline") + add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"]) + +end diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 603bf9beec6d..4a4e192baba3 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -527,6 +527,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableWebPerformanceAPIsByDefault: { + defaultValue: false, + metadata: { + description: + 'Enable Web Performance APIs (Performance Timeline, User Timings, etc.) by default.', + expectedReleaseValue: true, + purpose: 'release', + }, + ossReleaseStage: 'none', + }, fixMappingOfEventPrioritiesBetweenFabricAndReact: { defaultValue: false, metadata: { diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 7dff2b950633..2d47c7985b98 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -142,6 +142,7 @@ def use_react_native! ( pod 'React-featureflagsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/featureflags" pod 'React-microtasksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/microtasks" pod 'React-idlecallbacksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/idlecallbacks" + pod 'React-webperformancenativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/webperformance" pod 'React-domnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/dom" pod 'React-defaultsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/defaults" pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon" diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index d3a774635231..742d9bc1fad5 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1a00c3c154cd9e8afac7ad9333152ac6>> + * @generated SignedSource<<08b6cc65abf58a5e1018f861f9f2a5c8>> * @flow strict * @noformat */ @@ -95,6 +95,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableVirtualViewDebugFeatures: Getter, enableVirtualViewRenderState: Getter, enableVirtualViewWindowFocusDetection: Getter, + enableWebPerformanceAPIsByDefault: Getter, fixMappingOfEventPrioritiesBetweenFabricAndReact: Getter, fuseboxEnabledRelease: Getter, fuseboxNetworkInspectionEnabled: Getter, @@ -381,6 +382,10 @@ export const enableVirtualViewRenderState: Getter = createNativeFlagGet * Enables window focus detection for prioritizing VirtualView events. */ export const enableVirtualViewWindowFocusDetection: Getter = createNativeFlagGetter('enableVirtualViewWindowFocusDetection', false); +/** + * Enable Web Performance APIs (Performance Timeline, User Timings, etc.) by default. + */ +export const enableWebPerformanceAPIsByDefault: Getter = createNativeFlagGetter('enableWebPerformanceAPIsByDefault', false); /** * Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 8d802da16a56..259cbe56e53f 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<3e23fd36db655942220a765e2054f01f>> * @flow strict * @noformat */ @@ -70,6 +70,7 @@ export interface Spec extends TurboModule { +enableVirtualViewDebugFeatures?: () => boolean; +enableVirtualViewRenderState?: () => boolean; +enableVirtualViewWindowFocusDetection?: () => boolean; + +enableWebPerformanceAPIsByDefault?: () => boolean; +fixMappingOfEventPrioritiesBetweenFabricAndReact?: () => boolean; +fuseboxEnabledRelease?: () => boolean; +fuseboxNetworkInspectionEnabled?: () => boolean; diff --git a/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js b/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js new file mode 100644 index 000000000000..3b8180b36c7d --- /dev/null +++ b/packages/rn-tester/js/examples/Performance/PerformanceApiExample.js @@ -0,0 +1,270 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; +import type Performance from 'react-native/src/private/webapis/performance/Performance'; + +import RNTesterText from '../../components/RNTesterText'; +import * as React from 'react'; +import {useEffect} from 'react'; +import {Button, StyleSheet, View} from 'react-native'; + +declare var performance: Performance; + +const {useState, useCallback} = React; + +function MemoryExample(): React.Node { + // Memory API testing + const [memoryInfo, setMemoryInfo] = + useState(null); + const onGetMemoryInfo = useCallback(() => { + // performance.memory is not included in bom.js yet. + // Once we release the change in flow this can be removed. + setMemoryInfo(performance.memory); + }, []); + return ( + +