From 48998b4c111fadeeaf68201105888847127af2ca Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 4 Sep 2025 03:06:33 -0700 Subject: [PATCH 0001/1071] 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 0002/1071] 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 0003/1071] 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 0004/1071] 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 0005/1071] 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 0006/1071] 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 0007/1071] 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 0008/1071] 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 0009/1071] 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 0010/1071] 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 0011/1071] 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 0012/1071] 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 0013/1071] 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 0014/1071] 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 0015/1071] 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 0016/1071] 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 0017/1071] 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 0018/1071] 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 0019/1071] 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 0020/1071] 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 0021/1071] 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 0022/1071] 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 0023/1071] 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 0024/1071] 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 0025/1071] 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 0026/1071] 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 0027/1071] 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 0028/1071] 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 0029/1071] 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 0030/1071] 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 0031/1071] 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 0032/1071] 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 0033/1071] 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 0034/1071] 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 0035/1071] 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 0036/1071] 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 0037/1071] 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 0038/1071] 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 0039/1071] 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 0040/1071] 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 0041/1071] 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 0042/1071] 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 ( + +