From b9badd7474756a49a7fce51b508cffb26276e7a4 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 09:22:22 -0700 Subject: [PATCH 1/7] Bridgeless: Introduce main queue setup api in turbo modules (#49957) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49957 ## Changes This diff introduces the api for "main queue modules" into turbo modules. This will occur occurs before any rendering. ## Rationale Rendering can now include main -> js sync calls. If we allow js -> main sync calls during rendering, react native can deadlock. With this diff, we can move the js -> main sync calls to before any rendering happens. ## APIs **Buck API:** Plugin: ``` react_module_plugin_providers( name = "AccessibilityManager", native_class_func = "RCTAccessibilityManagerCls", unstable_requires_main_queue_setup = True, ) ``` **OSS API:** [codegenConfig](https://reactnative.dev/docs/the-new-architecture/using-codegen) in package.json: ``` "codegenConfig": { "name": "", "type": "", "jsSrcsDir": "", "android": { "javaPackageName": "" }, "ios": { "modules": { "AccessibilityManager": { "className": "RCTAccessibilityManager", "unstableRequiresMainQueueSetup": true } } } }, ``` Changelog: [iOS][Added] Introduce unstableRequiresMainQueueSetup api to modules Differential Revision: D70413478 Reviewed By: cipolleschi --- .../Libraries/AppDelegate/RCTAppSetupUtils.h | 2 + .../Libraries/AppDelegate/RCTAppSetupUtils.mm | 6 ++ .../RCTDefaultReactNativeFactoryDelegate.mm | 5 + .../AppDelegate/RCTDependencyProvider.h | 2 + .../AppDelegate/RCTReactNativeFactory.mm | 11 +++ .../platform/ios/ReactCommon/RCTHost.h | 2 + .../platform/ios/ReactCommon/RCTHost.mm | 8 ++ .../platform/ios/ReactCommon/RCTInstance.h | 2 + .../test-app/lib/test-library/package.json | 14 ++- .../__test_fixtures__/test-app/package.json | 14 ++- .../generate-artifacts-executor-test.js.snap | 57 ++++++++++++ .../generate-artifacts-executor-test.js | 2 + ...eModulesRequiringMainQueueSetupProvider.js | 92 +++++++++++++++++++ .../generate-artifacts-executor/index.js | 7 ++ .../RCTAppDependencyProviderMM.template | 5 + ...sRequiringMainQueueSetupProviderH.template | 14 +++ ...RequiringMainQueueSetupProviderMM.template | 19 ++++ 17 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 packages/react-native/scripts/codegen/generate-artifacts-executor/generateUnstableModulesRequiringMainQueueSetupProvider.js create mode 100644 packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderH.template create mode 100644 packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderMM.template diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h index d3b4f88de504..6cda68d9b62e 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h @@ -32,6 +32,8 @@ namespace facebook::react { class RuntimeScheduler; } +RCT_EXTERN NSArray *RCTAppSetupUnstableModulesRequiringMainQueueSetup( + id dependencyProvider); RCT_EXTERN id RCTAppSetupDefaultModuleFromClass( Class moduleClass, diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm index 21f007dba4e0..cf48ec9cef83 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm @@ -54,6 +54,12 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) return [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; } +NSArray *RCTAppSetupUnstableModulesRequiringMainQueueSetup(id dependencyProvider) +{ + // For oss, insert core main queue setup modules here + return dependencyProvider ? dependencyProvider.unstableModulesRequiringMainQueueSetup : @[]; +} + id RCTAppSetupDefaultModuleFromClass(Class moduleClass, id dependencyProvider) { // private block used to filter out modules depending on protocol conformance diff --git a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm index 7c8c8496800c..2a0f737a22a4 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm @@ -92,6 +92,11 @@ - (void)hostDidStart:(RCTHost *)host { } +- (NSArray *)unstableModulesRequiringMainQueueSetup +{ + return self.dependencyProvider ? RCTAppSetupUnstableModulesRequiringMainQueueSetup(self.dependencyProvider) : @[]; +} + - (nullable id)getModuleProvider:(const char *)name { NSString *providerName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; diff --git a/packages/react-native/Libraries/AppDelegate/RCTDependencyProvider.h b/packages/react-native/Libraries/AppDelegate/RCTDependencyProvider.h index bf2d8c91a49a..ddc412b0f71b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTDependencyProvider.h +++ b/packages/react-native/Libraries/AppDelegate/RCTDependencyProvider.h @@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)URLRequestHandlerClassNames; +- (NSArray *)unstableModulesRequiringMainQueueSetup; + - (NSDictionary> *)thirdPartyFabricComponents; - (nonnull NSDictionary> *)moduleProviders; diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm index 4e03a6e37ac8..b97ae0614df2 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm @@ -229,6 +229,17 @@ - (void)hostDidStart:(RCTHost *)host } } +- (NSArray *)unstableModulesRequiringMainQueueSetup +{ +#if RN_DISABLE_OSS_PLUGIN_HEADER + return RCTTurboModulePluginUnstableModulesRequiringMainQueueSetup(); +#else + return self.delegate.dependencyProvider + ? RCTAppSetupUnstableModulesRequiringMainQueueSetup(self.delegate.dependencyProvider) + : @[]; +#endif +} + - (RCTRootViewFactory *)createRCTRootViewFactory { __weak __typeof(self) weakSelf = self; diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h index 8d8bcd5fcb1a..c72cf7cdd6f6 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h @@ -30,6 +30,8 @@ typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void); - (void)hostDidStart:(RCTHost *)host; @optional +- (NSArray *)unstableModulesRequiringMainQueueSetup; + - (void)loadBundleAtURL:(NSURL *)sourceURL onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)loadCallback; diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index 8f6f7f3a466d..d0a4e7c5ec80 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -307,6 +307,14 @@ - (void)dealloc #pragma mark - RCTInstanceDelegate +- (NSArray *)unstableModulesRequiringMainQueueSetup +{ + if ([_hostDelegate respondsToSelector:@selector(unstableModulesRequiringMainQueueSetup)]) { + return [_hostDelegate unstableModulesRequiringMainQueueSetup]; + } + return @[]; +} + - (BOOL)instance:(RCTInstance *)instance didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h index 0593dccc87a0..5d4ea87be9e9 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h @@ -43,6 +43,8 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)loadCallback; +- (NSArray *)unstableModulesRequiringMainQueueSetup; + // TODO(T205780509): Remove this api in react native v0.78 // The bridgeless js error handling api will just call into exceptionsmanager directly - (BOOL)instance:(RCTInstance *)instance diff --git a/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json index ea211b7e9cee..c53ab311fe04 100644 --- a/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json +++ b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json @@ -34,18 +34,26 @@ "componentProvider": { "TestLibraryDeprecatedComponent": "RCTTestLibraryDeprecatedComponentClass" }, + "unstableModulesRequiringMainQueueSetup": [ + "RCTTestLibraryDeprecatedImageURLLoader", + "RCTTestLibraryDeprecatedURLRequestHandler", + "RCTTestLibraryDeprecatedImageDataDecoder" + ], "modules": { "TestLibraryImageURLLoader": { "conformsToProtocols": ["RCTImageURLLoader"], - "className": "RCTTestLibraryImageURLLoader" + "className": "RCTTestLibraryImageURLLoader", + "unstableRequiresMainQueueSetup": true }, "TestLibraryURLRequestHandler": { "conformsToProtocols": ["RCTURLRequestHandler"], - "className": "RCTTestLibraryURLRequestHandler" + "className": "RCTTestLibraryURLRequestHandler", + "unstableRequiresMainQueueSetup": true }, "TestLibraryImageDataDecoder": { "conformsToProtocols": ["RCTImageDataDecoder"], - "className": "RCTTestLibraryImageDataDecoder" + "className": "RCTTestLibraryImageDataDecoder", + "unstableRequiresMainQueueSetup": true } }, "components": { diff --git a/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json index d48f1ad826e4..44fd37bd54b3 100644 --- a/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json +++ b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json @@ -35,18 +35,26 @@ "componentProvider": { "TestAppDeprecatedComponent": "RCTTestAppDeprecatedComponentClass" }, + "unstableModulesRequiringMainQueueSetup": [ + "RCTTestAppDeprecatedImageURLLoader", + "RCTTestAppDeprecatedURLRequestHandler", + "RCTTestAppDeprecatedImageDataDecoder" + ], "modules": { "TestAppImageURLLoader": { "conformsToProtocols": ["RCTImageURLLoader"], - "className": "RCTTestAppImageURLLoader" + "className": "RCTTestAppImageURLLoader", + "unstableRequiresMainQueueSetup": true }, "TestAppURLRequestHandler": { "conformsToProtocols": ["RCTURLRequestHandler"], - "className": "RCTTestAppURLRequestHandler" + "className": "RCTTestAppURLRequestHandler", + "unstableRequiresMainQueueSetup": true }, "TestAppImageDataDecoder": { "conformsToProtocols": ["RCTImageDataDecoder"], - "className": "RCTTestAppImageDataDecoder" + "className": "RCTTestAppImageDataDecoder", + "unstableRequiresMainQueueSetup": true } }, "components": { diff --git a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap index 0431f599e3cf..38256dd89bca 100644 --- a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap +++ b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap @@ -40,6 +40,7 @@ exports[`execute "RCTAppDependencyProvider.mm" should match snapshot 1`] = ` #import \\"RCTAppDependencyProvider.h\\" #import #import +#import #import @implementation RCTAppDependencyProvider @@ -56,6 +57,10 @@ exports[`execute "RCTAppDependencyProvider.mm" should match snapshot 1`] = ` return RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames; } +- (nonnull NSArray *)unstableModulesRequiringMainQueueSetup { + return RCTUnstableModulesRequiringMainQueueSetupProvider.modules; +} + - (nonnull NSDictionary> *)thirdPartyFabricComponents { return RCTThirdPartyComponentsProvider.thirdPartyFabricComponents; } @@ -300,6 +305,58 @@ exports[`execute "RCTThirdPartyComponentsProvider.mm" should match snapshot 1`] " `; +exports[`execute "RCTUnstableModulesRequiringMainQueueSetupProvider.h" should match snapshot 1`] = ` +"/* + * 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. + */ + +#import + +@interface RCTUnstableModulesRequiringMainQueueSetupProvider: NSObject + ++(NSArray *)modules; + +@end +" +`; + +exports[`execute "RCTUnstableModulesRequiringMainQueueSetupProvider.mm" should match snapshot 1`] = ` +"/* + * 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. + */ + +#import \\"RCTUnstableModulesRequiringMainQueueSetupProvider.h\\" + +@implementation RCTUnstableModulesRequiringMainQueueSetupProvider + ++(NSArray *)modules +{ + return @[ + @\\"RCTTestAppDeprecatedImageURLLoader\\", + @\\"RCTTestAppDeprecatedURLRequestHandler\\", + @\\"RCTTestAppDeprecatedImageDataDecoder\\", + @\\"RCTTestLibraryDeprecatedImageURLLoader\\", + @\\"RCTTestLibraryDeprecatedURLRequestHandler\\", + @\\"RCTTestLibraryDeprecatedImageDataDecoder\\", + @\\"TestAppImageURLLoader\\", + @\\"TestAppURLRequestHandler\\", + @\\"TestAppImageDataDecoder\\", + @\\"TestLibraryImageURLLoader\\", + @\\"TestLibraryURLRequestHandler\\", + @\\"TestLibraryImageDataDecoder\\" + ]; +} + +@end +" +`; + exports[`execute "ReactAppDependencyProvider.podspec" should match snapshot 1`] = ` "# Copyright (c) Meta Platforms, Inc. and affiliates. # diff --git a/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js index 139ec02b23b9..0967f22e8f44 100644 --- a/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js +++ b/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -51,6 +51,8 @@ describe('execute', () => { 'RCTThirdPartyComponentsProvider.mm', 'ReactAppDependencyProvider.podspec', 'ReactCodegen.podspec', + 'RCTUnstableModulesRequiringMainQueueSetupProvider.h', + 'RCTUnstableModulesRequiringMainQueueSetupProvider.mm', ].forEach(file => { it(`"${file}" should match snapshot`, () => { const generatedFileDir = path.join(outputDir, 'build/generated/ios'); diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/generateUnstableModulesRequiringMainQueueSetupProvider.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/generateUnstableModulesRequiringMainQueueSetupProvider.js new file mode 100644 index 000000000000..4187a53bf2d7 --- /dev/null +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/generateUnstableModulesRequiringMainQueueSetupProvider.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. + * + * @format + */ + +'use strict'; + +const {TEMPLATES_FOLDER_PATH} = require('./constants'); +const {parseiOSAnnotations} = require('./utils'); +const fs = require('fs'); +const path = require('path'); + +const UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_H_TEMPLATE_PATH = + path.join( + TEMPLATES_FOLDER_PATH, + 'RCTUnstableModulesRequiringMainQueueSetupProviderH.template', + ); + +const UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_MM_TEMPLATE_PATH = + path.join( + TEMPLATES_FOLDER_PATH, + 'RCTUnstableModulesRequiringMainQueueSetupProviderMM.template', + ); + +function generateUnstableModulesRequiringMainQueueSetupProvider( + libraries, + outputDir, +) { + const iosAnnotations = parseiOSAnnotations(libraries); + + const modulesRequiringMainQueueSetup = new Set(); + + // Old API + libraries.forEach(library => { + const {unstableModulesRequiringMainQueueSetup} = library?.config?.ios || {}; + if (!unstableModulesRequiringMainQueueSetup) { + return; + } + + for (const moduleName of unstableModulesRequiringMainQueueSetup) { + modulesRequiringMainQueueSetup.add(moduleName); + } + }); + + // New API + for (const {modules: moduleAnnotationMap} of Object.values(iosAnnotations)) { + for (const [moduleName, annotation] of Object.entries( + moduleAnnotationMap, + )) { + if (annotation.unstableRequiresMainQueueSetup) { + modulesRequiringMainQueueSetup.add(moduleName); + } + } + } + + const modulesStr = Array.from(modulesRequiringMainQueueSetup) + .map(className => `@"${className}"`) + .join(',\n\t\t'); + + const template = fs.readFileSync( + UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_MM_TEMPLATE_PATH, + 'utf8', + ); + const finalMMFile = template.replace(/{modules}/, modulesStr); + + fs.mkdirSync(outputDir, {recursive: true}); + + fs.writeFileSync( + path.join( + outputDir, + 'RCTUnstableModulesRequiringMainQueueSetupProvider.mm', + ), + finalMMFile, + ); + + const templateH = fs.readFileSync( + UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_H_TEMPLATE_PATH, + 'utf8', + ); + fs.writeFileSync( + path.join(outputDir, 'RCTUnstableModulesRequiringMainQueueSetupProvider.h'), + templateH, + ); +} + +module.exports = { + generateUnstableModulesRequiringMainQueueSetupProvider, +}; diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js index ce4c7d802584..4b9ca0e50144 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js @@ -27,6 +27,9 @@ const { } = require('./generateRCTThirdPartyComponents'); const {generateReactCodegenPodspec} = require('./generateReactCodegenPodspec'); const {generateSchemaInfos} = require('./generateSchemaInfos'); +const { + generateUnstableModulesRequiringMainQueueSetupProvider, +} = require('./generateUnstableModulesRequiringMainQueueSetupProvider'); const { buildCodegenIfNeeded, cleanupEmptyFilesAndFolders, @@ -115,6 +118,10 @@ function execute( generateRCTThirdPartyComponents(libraries, outputPath); generateRCTModuleProviders(projectRoot, pkgJson, libraries, outputPath); generateCustomURLHandlers(libraries, outputPath); + generateUnstableModulesRequiringMainQueueSetupProvider( + libraries, + outputPath, + ); generateAppDependencyProvider(outputPath); } generateReactCodegenPodspec( diff --git a/packages/react-native/scripts/codegen/templates/RCTAppDependencyProviderMM.template b/packages/react-native/scripts/codegen/templates/RCTAppDependencyProviderMM.template index e19c096e1e7d..b76c468c8a87 100644 --- a/packages/react-native/scripts/codegen/templates/RCTAppDependencyProviderMM.template +++ b/packages/react-native/scripts/codegen/templates/RCTAppDependencyProviderMM.template @@ -8,6 +8,7 @@ #import "RCTAppDependencyProvider.h" #import #import +#import #import @implementation RCTAppDependencyProvider @@ -24,6 +25,10 @@ return RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames; } +- (nonnull NSArray *)unstableModulesRequiringMainQueueSetup { + return RCTUnstableModulesRequiringMainQueueSetupProvider.modules; +} + - (nonnull NSDictionary> *)thirdPartyFabricComponents { return RCTThirdPartyComponentsProvider.thirdPartyFabricComponents; } diff --git a/packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderH.template b/packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderH.template new file mode 100644 index 000000000000..114d32253abe --- /dev/null +++ b/packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderH.template @@ -0,0 +1,14 @@ +/* + * 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. + */ + +#import + +@interface RCTUnstableModulesRequiringMainQueueSetupProvider: NSObject + ++(NSArray *)modules; + +@end diff --git a/packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderMM.template b/packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderMM.template new file mode 100644 index 000000000000..3f29f3576760 --- /dev/null +++ b/packages/react-native/scripts/codegen/templates/RCTUnstableModulesRequiringMainQueueSetupProviderMM.template @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#import "RCTUnstableModulesRequiringMainQueueSetupProvider.h" + +@implementation RCTUnstableModulesRequiringMainQueueSetupProvider + ++(NSArray *)modules +{ + return @[ + {modules} + ]; +} + +@end From 9450a827cfd7ad35ed9e91a772162bcb27ac3a36 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 09:35:01 -0700 Subject: [PATCH 2/7] Bridgeless: Implement main queue setup api in turbo modules (#50040) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50040 This diff implements main queue module setup. Sometimes, people need to capture uikit things, and use them from javascript. In those cases, people can write main queue modules. These modules will be eagerly initialized on the main queue, during react native init. ## On Necessity **Sync** dispatches to the main thread from the js thread can deadlock react native. And **async** dispatches to the main thread from the js thread sometimes might not be enough: it could lead to flickery rendering. So, we need to allow people to capture ui thread things, before any js executes. ## Caveat This api is dangerous and discouraged. All react native surfaces will pay the cost of one surface introducing a main queue module. It could also slow down common/critical interactions in your app, if you're not careful. We will introduce performance logging for this infrastructure. So that we can monitor and file tasks, when main queue module init starts taking "too long." Changelog: [General][Breaking]: Introduce beforeload callback arg into ReactInstance::loadScript Differential Revision: D71084243 Reviewed By: mdvacca --- .../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 | 74 ++++++++++++------- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 ++- ...eactNativeFeatureFlagsOverridesOSSCanary.h | 4 +- ...tiveFeatureFlagsOverridesOSSExperimental.h | 4 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../react/runtime/ReactInstance.cpp | 13 +++- .../ReactCommon/react/runtime/ReactInstance.h | 3 +- .../platform/ios/ReactCommon/RCTInstance.mm | 39 +++++++++- .../ReactNativeFeatureFlags.config.js | 10 +++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 25 files changed, 217 insertions(+), 56 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 f7c5264dda74..7ff9e196fcd9 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<<49a61b2330d748fc05bd5e8ce042bf72>> */ /** @@ -118,6 +118,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableLongTaskAPI(): Boolean = accessor.enableLongTaskAPI() + /** + * Makes modules requiring main queue setup initialize on the main thread, during React Native init. + */ + @JvmStatic + public fun enableMainQueueModulesOnIOS(): Boolean = accessor.enableMainQueueModulesOnIOS() + /** * Parse CSS strings using the Fabric CSS parser instead of ViewConfig processing */ 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 a7f7bee1fe6e..ebec84ceb91c 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<<9813fd15c7f54d69d3de6084f733c3a6>> + * @generated SignedSource<<86fe1d1217f974eb9cbe2fc6b556b6f3>> */ /** @@ -35,6 +35,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableLayoutAnimationsOnAndroidCache: Boolean? = null private var enableLayoutAnimationsOnIOSCache: Boolean? = null private var enableLongTaskAPICache: Boolean? = null + private var enableMainQueueModulesOnIOSCache: Boolean? = null private var enableNativeCSSParsingCache: Boolean? = null private var enableNewBackgroundAndBorderDrawablesCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null @@ -198,6 +199,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableMainQueueModulesOnIOS(): Boolean { + var cached = enableMainQueueModulesOnIOSCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableMainQueueModulesOnIOS() + enableMainQueueModulesOnIOSCache = cached + } + return cached + } + override fun enableNativeCSSParsing(): Boolean { var cached = enableNativeCSSParsingCache 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 0f0bdfe2b6b1..5607cab41a62 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<<06c5ef75b624d953e7a8d9297feaf816>> + * @generated SignedSource<> */ /** @@ -58,6 +58,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableLongTaskAPI(): Boolean + @DoNotStrip @JvmStatic public external fun enableMainQueueModulesOnIOS(): Boolean + @DoNotStrip @JvmStatic public external fun enableNativeCSSParsing(): Boolean @DoNotStrip @JvmStatic public external fun enableNewBackgroundAndBorderDrawables(): 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 d7f4afb4a83d..ce0e00a74f57 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<> */ /** @@ -53,6 +53,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableLongTaskAPI(): Boolean = false + override fun enableMainQueueModulesOnIOS(): Boolean = false + override fun enableNativeCSSParsing(): Boolean = false override fun enableNewBackgroundAndBorderDrawables(): 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 a958b7c90cb9..6552ac9f737c 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<<0fb76e329360157ea9772a0d8bebc873>> + * @generated SignedSource<<5fc2a365b51c5e14f99ec1d3b162f4e3>> */ /** @@ -39,6 +39,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableLayoutAnimationsOnAndroidCache: Boolean? = null private var enableLayoutAnimationsOnIOSCache: Boolean? = null private var enableLongTaskAPICache: Boolean? = null + private var enableMainQueueModulesOnIOSCache: Boolean? = null private var enableNativeCSSParsingCache: Boolean? = null private var enableNewBackgroundAndBorderDrawablesCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null @@ -217,6 +218,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableMainQueueModulesOnIOS(): Boolean { + var cached = enableMainQueueModulesOnIOSCache + if (cached == null) { + cached = currentProvider.enableMainQueueModulesOnIOS() + accessedFeatureFlags.add("enableMainQueueModulesOnIOS") + enableMainQueueModulesOnIOSCache = cached + } + return cached + } + override fun enableNativeCSSParsing(): Boolean { var cached = enableNativeCSSParsingCache 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 9d194b9ce6af..24795a5698bd 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<<0c16f7af743a6c4d7aaabf010c1c7327>> + * @generated SignedSource<<1db9978c9096c9775fd7cd7d37637c8c>> */ /** @@ -53,6 +53,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableLongTaskAPI(): Boolean + @DoNotStrip public fun enableMainQueueModulesOnIOS(): Boolean + @DoNotStrip public fun enableNativeCSSParsing(): Boolean @DoNotStrip public fun enableNewBackgroundAndBorderDrawables(): 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 c720ea8b1bc6..52ae0e0e2bb0 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<<696c541ad3725d2d39eaaf8b9b4a317c>> */ /** @@ -129,6 +129,12 @@ class ReactNativeFeatureFlagsProviderHolder return method(javaProvider_); } + bool enableMainQueueModulesOnIOS() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableMainQueueModulesOnIOS"); + return method(javaProvider_); + } + bool enableNativeCSSParsing() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableNativeCSSParsing"); @@ -370,6 +376,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableLongTaskAPI( return ReactNativeFeatureFlags::enableLongTaskAPI(); } +bool JReactNativeFeatureFlagsCxxInterop::enableMainQueueModulesOnIOS( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableMainQueueModulesOnIOS(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableNativeCSSParsing( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableNativeCSSParsing(); @@ -581,6 +592,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableLongTaskAPI", JReactNativeFeatureFlagsCxxInterop::enableLongTaskAPI), + makeNativeMethod( + "enableMainQueueModulesOnIOS", + JReactNativeFeatureFlagsCxxInterop::enableMainQueueModulesOnIOS), makeNativeMethod( "enableNativeCSSParsing", JReactNativeFeatureFlagsCxxInterop::enableNativeCSSParsing), 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 08f10d5dad3b..381b3596d9c4 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<> */ /** @@ -75,6 +75,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableLongTaskAPI( facebook::jni::alias_ref); + static bool enableMainQueueModulesOnIOS( + facebook::jni::alias_ref); + static bool enableNativeCSSParsing( 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 ece27d06f80a..c375c6fb0aad 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<<4d47f61435ba3a74e84752a02def81c2>> + * @generated SignedSource<<07e6cf68cec4f7c7d61de4958b11ff4f>> */ /** @@ -86,6 +86,10 @@ bool ReactNativeFeatureFlags::enableLongTaskAPI() { return getAccessor().enableLongTaskAPI(); } +bool ReactNativeFeatureFlags::enableMainQueueModulesOnIOS() { + return getAccessor().enableMainQueueModulesOnIOS(); +} + bool ReactNativeFeatureFlags::enableNativeCSSParsing() { return getAccessor().enableNativeCSSParsing(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index e6536c2895ef..7d9a1c44a56c 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<<29cdbefbe02064df4fb65388e64f95a4>> + * @generated SignedSource<> */ /** @@ -114,6 +114,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableLongTaskAPI(); + /** + * Makes modules requiring main queue setup initialize on the main thread, during React Native init. + */ + RN_EXPORT static bool enableMainQueueModulesOnIOS(); + /** * Parse CSS strings using the Fabric CSS parser instead of ViewConfig processing */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 91bf73707b40..d335d93a8165 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<<353677101629176192534f1f234e2de4>> + * @generated SignedSource<<40daa9d9fc429b26ec6c178a6d9f6c5d>> */ /** @@ -299,6 +299,24 @@ bool ReactNativeFeatureFlagsAccessor::enableLongTaskAPI() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableMainQueueModulesOnIOS() { + auto flagValue = enableMainQueueModulesOnIOS_.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(15, "enableMainQueueModulesOnIOS"); + + flagValue = currentProvider_->enableMainQueueModulesOnIOS(); + enableMainQueueModulesOnIOS_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { auto flagValue = enableNativeCSSParsing_.load(); @@ -308,7 +326,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(15, "enableNativeCSSParsing"); + markFlagAsAccessed(16, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -326,7 +344,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(16, "enableNewBackgroundAndBorderDrawables"); + markFlagAsAccessed(17, "enableNewBackgroundAndBorderDrawables"); flagValue = currentProvider_->enableNewBackgroundAndBorderDrawables(); enableNewBackgroundAndBorderDrawables_ = flagValue; @@ -344,7 +362,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(17, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(18, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -362,7 +380,7 @@ bool ReactNativeFeatureFlagsAccessor::enableReportEventPaintTime() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(18, "enableReportEventPaintTime"); + markFlagAsAccessed(19, "enableReportEventPaintTime"); flagValue = currentProvider_->enableReportEventPaintTime(); enableReportEventPaintTime_ = flagValue; @@ -380,7 +398,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSynchronousStateUpdates() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(19, "enableSynchronousStateUpdates"); + markFlagAsAccessed(20, "enableSynchronousStateUpdates"); flagValue = currentProvider_->enableSynchronousStateUpdates(); enableSynchronousStateUpdates_ = flagValue; @@ -398,7 +416,7 @@ bool ReactNativeFeatureFlagsAccessor::enableUIConsistency() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(20, "enableUIConsistency"); + markFlagAsAccessed(21, "enableUIConsistency"); flagValue = currentProvider_->enableUIConsistency(); enableUIConsistency_ = flagValue; @@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(21, "enableViewCulling"); + markFlagAsAccessed(22, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(22, "enableViewRecycling"); + markFlagAsAccessed(23, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -452,7 +470,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(23, "enableViewRecyclingForText"); + markFlagAsAccessed(24, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -470,7 +488,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(24, "enableViewRecyclingForView"); + markFlagAsAccessed(25, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::fixDifferentiatorEmittingUpdatesWithWrongP // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "fixDifferentiatorEmittingUpdatesWithWrongParentTag"); + markFlagAsAccessed(26, "fixDifferentiatorEmittingUpdatesWithWrongParentTag"); flagValue = currentProvider_->fixDifferentiatorEmittingUpdatesWithWrongParentTag(); fixDifferentiatorEmittingUpdatesWithWrongParentTag_ = flagValue; @@ -506,7 +524,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(26, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(27, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMountingCoordinatorReportedPendingTrans // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(27, "fixMountingCoordinatorReportedPendingTransactionsOnAndroid"); + markFlagAsAccessed(28, "fixMountingCoordinatorReportedPendingTransactionsOnAndroid"); flagValue = currentProvider_->fixMountingCoordinatorReportedPendingTransactionsOnAndroid(); fixMountingCoordinatorReportedPendingTransactionsOnAndroid_ = flagValue; @@ -542,7 +560,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(28, "fuseboxEnabledRelease"); + markFlagAsAccessed(29, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -560,7 +578,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(29, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(30, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::lazyAnimationCallbacks() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "lazyAnimationCallbacks"); + markFlagAsAccessed(31, "lazyAnimationCallbacks"); flagValue = currentProvider_->lazyAnimationCallbacks(); lazyAnimationCallbacks_ = flagValue; @@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::removeTurboModuleManagerDelegateMutex() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(31, "removeTurboModuleManagerDelegateMutex"); + markFlagAsAccessed(32, "removeTurboModuleManagerDelegateMutex"); flagValue = currentProvider_->removeTurboModuleManagerDelegateMutex(); removeTurboModuleManagerDelegateMutex_ = flagValue; @@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::throwExceptionInsteadOfDeadlockOnTurboModu // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(32, "throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS"); + markFlagAsAccessed(33, "throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS"); flagValue = currentProvider_->throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(); throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS_ = flagValue; @@ -632,7 +650,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(33, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(34, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -650,7 +668,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(34, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(35, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::useEditTextStockAndroidFocusBehavior() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "useEditTextStockAndroidFocusBehavior"); + markFlagAsAccessed(36, "useEditTextStockAndroidFocusBehavior"); flagValue = currentProvider_->useEditTextStockAndroidFocusBehavior(); useEditTextStockAndroidFocusBehavior_ = flagValue; @@ -686,7 +704,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(36, "useFabricInterop"); + markFlagAsAccessed(37, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -704,7 +722,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(37, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(38, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -722,7 +740,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(38, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(39, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -740,7 +758,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(39, "useRawPropsJsiValue"); + markFlagAsAccessed(40, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -758,7 +776,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(40, "useTurboModuleInterop"); + markFlagAsAccessed(41, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -776,7 +794,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(41, "useTurboModules"); + markFlagAsAccessed(42, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 1d0e986c4b4f..cd53f268d2bc 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<<00f623d5f3920895eb2c1b530dce6a7d>> + * @generated SignedSource<<54364d504177fce95cc2cbbc2b55cb65>> */ /** @@ -47,6 +47,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableLayoutAnimationsOnAndroid(); bool enableLayoutAnimationsOnIOS(); bool enableLongTaskAPI(); + bool enableMainQueueModulesOnIOS(); bool enableNativeCSSParsing(); bool enableNewBackgroundAndBorderDrawables(); bool enablePropsUpdateReconciliationAndroid(); @@ -85,7 +86,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 42> accessedFeatureFlags_; + std::array, 43> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> animatedShouldSignalBatch_; @@ -102,6 +103,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableLayoutAnimationsOnAndroid_; std::atomic> enableLayoutAnimationsOnIOS_; std::atomic> enableLongTaskAPI_; + std::atomic> enableMainQueueModulesOnIOS_; std::atomic> enableNativeCSSParsing_; std::atomic> enableNewBackgroundAndBorderDrawables_; std::atomic> enablePropsUpdateReconciliationAndroid_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 4cb62cd067f5..8bcbc0dd95bd 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<<140e6580100dab5806194e780610f122>> + * @generated SignedSource<> */ /** @@ -87,6 +87,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableMainQueueModulesOnIOS() override { + return false; + } + bool enableNativeCSSParsing() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index bfd2a802d94b..e3c119f7dadb 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<<28915a06a3c24b532384b481cc91d863>> */ /** @@ -180,6 +180,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableLongTaskAPI(); } + bool enableMainQueueModulesOnIOS() override { + auto value = values_["enableMainQueueModulesOnIOS"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableMainQueueModulesOnIOS(); + } + bool enableNativeCSSParsing() override { auto value = values_["enableNativeCSSParsing"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h index 3dbcd2356ccb..55ad9008b618 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.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<<53c858fa9ec8c4e3b9849f7513fdb441>> */ /** @@ -100,6 +100,8 @@ class ReactNativeFeatureFlagsOverridesOSSCanary : public ReactNativeFeatureFlags + + diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h index 0d63daa28ba7..6925f5f629f8 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.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<> */ /** @@ -107,6 +107,8 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur + + diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index e9ddbd4c1c9e..e05009ab0ef4 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<<0845c9b1618ac1e18ce4aeb5d03a70f5>> + * @generated SignedSource<<3d6eb54a5f64b0d5c72dae44e669052e>> */ /** @@ -40,6 +40,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableLayoutAnimationsOnAndroid() = 0; virtual bool enableLayoutAnimationsOnIOS() = 0; virtual bool enableLongTaskAPI() = 0; + virtual bool enableMainQueueModulesOnIOS() = 0; virtual bool enableNativeCSSParsing() = 0; virtual bool enableNewBackgroundAndBorderDrawables() = 0; virtual bool enablePropsUpdateReconciliationAndroid() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 4f831b521d9c..061eb797c462 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<<7493403280d2ac7ec348eb3d78d915c2>> + * @generated SignedSource<> */ /** @@ -119,6 +119,11 @@ bool NativeReactNativeFeatureFlags::enableLongTaskAPI( return ReactNativeFeatureFlags::enableLongTaskAPI(); } +bool NativeReactNativeFeatureFlags::enableMainQueueModulesOnIOS( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableMainQueueModulesOnIOS(); +} + bool NativeReactNativeFeatureFlags::enableNativeCSSParsing( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableNativeCSSParsing(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 831c819ed98b..619b45d88fb9 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<<36b55f5cfd86d9aaad26c760dec7c875>> + * @generated SignedSource<> */ /** @@ -67,6 +67,8 @@ class NativeReactNativeFeatureFlags bool enableLongTaskAPI(jsi::Runtime& runtime); + bool enableMainQueueModulesOnIOS(jsi::Runtime& runtime); + bool enableNativeCSSParsing(jsi::Runtime& runtime); bool enableNewBackgroundAndBorderDrawables(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index 94ae9fe18b1e..db92e3e75ca4 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -215,7 +215,8 @@ std::string simpleBasename(const std::string& path) { void ReactInstance::loadScript( std::unique_ptr script, const std::string& sourceURL, - std::function&& completion) { + std::function&& beforeLoad, + std::function&& afterLoad) { auto buffer = std::make_shared(std::move(script)); std::string scriptName = simpleBasename(sourceURL); @@ -226,7 +227,11 @@ void ReactInstance::loadScript( weakBufferedRuntimeExecuter = std::weak_ptr( bufferedRuntimeExecutor_), - completion](jsi::Runtime& runtime) { + beforeLoad, + afterLoad](jsi::Runtime& runtime) { + if (beforeLoad) { + beforeLoad(runtime); + } TraceSection s("ReactInstance::loadScript"); bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl); if (hasLogger) { @@ -255,8 +260,8 @@ void ReactInstance::loadScript( weakBufferedRuntimeExecuter.lock()) { strongBufferedRuntimeExecuter->flush(); } - if (completion) { - completion(runtime); + if (afterLoad) { + afterLoad(runtime); } }); } diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.h b/packages/react-native/ReactCommon/react/runtime/ReactInstance.h index a1e40537ecc1..51709eebcd7a 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.h @@ -50,7 +50,8 @@ class ReactInstance final : private jsinspector_modern::InstanceTargetDelegate { void loadScript( std::unique_ptr script, const std::string& sourceURL, - std::function&& completion = nullptr); + std::function&& beforeLoad = nullptr, + std::function&& afterLoad = nullptr); void registerSegment(uint32_t segmentId, const std::string& segmentPath); diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 74f8178a029e..e06167df9640 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -85,6 +85,7 @@ @implementation RCTInstance { std::atomic _valid; RCTJSThreadManager *_jsThreadManager; NSDictionary *_launchOptions; + void (^_waitUntilModuleSetupComplete)(); // APIs supporting interop with native modules and view managers RCTBridgeModuleDecorator *_bridgeModuleDecorator; @@ -314,6 +315,33 @@ - (void)_start // Initialize RCTModuleRegistry so that TurboModules can require other TurboModules. [_bridgeModuleDecorator.moduleRegistry setTurboModuleRegistry:_turboModuleManager]; + if (ReactNativeFeatureFlags::enableMainQueueModulesOnIOS()) { + /** + * Some native modules need to capture uikit objects on the main thread. + * Start initializing those modules on the main queue here. The JavaScript thread + * will wait until this module init finishes, before executing the js bundle. + */ + NSArray *modulesRequiringMainQueueSetup = [_delegate unstableModulesRequiringMainQueueSetup]; + if ([modulesRequiringMainQueueSetup count] > 0) { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + _waitUntilModuleSetupComplete = ^{ + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) { + RCTLogError( + @"Timed out waiting for native modules to be setup on the main queue: %@", + modulesRequiringMainQueueSetup); + }; + }; + + // TODO(T218039767): Integrate perf logging into main queue module init + RCTExecuteOnMainQueue(^{ + for (NSString *moduleName in modulesRequiringMainQueueSetup) { + [self->_bridgeModuleDecorator.moduleRegistry moduleForName:[moduleName UTF8String]]; + } + dispatch_semaphore_signal(semaphore); + }); + } + } + RCTLogSetBridgelessModuleRegistry(_bridgeModuleDecorator.moduleRegistry); RCTLogSetBridgelessCallableJSModules(_bridgeModuleDecorator.callableJSModules); @@ -496,9 +524,16 @@ - (void)_loadScriptFromSource:(RCTSource *)source auto script = std::make_unique(source.data); const auto *url = deriveSourceURL(source.url).UTF8String; - _reactInstance->loadScript(std::move(script), url, [](jsi::Runtime &_) { + + auto beforeLoad = [waitUntilModuleSetupComplete = self->_waitUntilModuleSetupComplete](jsi::Runtime &_) { + if (waitUntilModuleSetupComplete) { + waitUntilModuleSetupComplete(); + } + }; + auto afterLoad = [](jsi::Runtime &_) { [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTInstanceDidLoadBundle" object:nil]; - }); + }; + _reactInstance->loadScript(std::move(script), url, beforeLoad, afterLoad); } - (void)_handleJSError:(const JsErrorHandler::ProcessedError &)error withRuntime:(jsi::Runtime &)runtime diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 59962f380d8a..d45d788fb620 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -202,6 +202,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableMainQueueModulesOnIOS: { + defaultValue: false, + metadata: { + description: + 'Makes modules requiring main queue setup initialize on the main thread, during React Native init.', + expectedReleaseValue: true, + purpose: 'release', + }, + ossReleaseStage: 'none', + }, enableNativeCSSParsing: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 34b354dd3302..02926ad1bed0 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<> + * @generated SignedSource<<8e4f25dbb96b6c7a9fe645d67172d0ab>> * @flow strict */ @@ -62,6 +62,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableLayoutAnimationsOnAndroid: Getter, enableLayoutAnimationsOnIOS: Getter, enableLongTaskAPI: Getter, + enableMainQueueModulesOnIOS: Getter, enableNativeCSSParsing: Getter, enableNewBackgroundAndBorderDrawables: Getter, enablePropsUpdateReconciliationAndroid: Getter, @@ -220,6 +221,10 @@ export const enableLayoutAnimationsOnIOS: Getter = createNativeFlagGett * Enables the reporting of long tasks through `PerformanceObserver`. Only works if the event loop is enabled. */ export const enableLongTaskAPI: Getter = createNativeFlagGetter('enableLongTaskAPI', false); +/** + * Makes modules requiring main queue setup initialize on the main thread, during React Native init. + */ +export const enableMainQueueModulesOnIOS: Getter = createNativeFlagGetter('enableMainQueueModulesOnIOS', false); /** * Parse CSS strings using the Fabric CSS parser instead of ViewConfig processing */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 522915743291..a571a2d24501 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<<22d8e7623a2eee5182c786f2ec914401>> + * @generated SignedSource<> * @flow strict */ @@ -39,6 +39,7 @@ export interface Spec extends TurboModule { +enableLayoutAnimationsOnAndroid?: () => boolean; +enableLayoutAnimationsOnIOS?: () => boolean; +enableLongTaskAPI?: () => boolean; + +enableMainQueueModulesOnIOS?: () => boolean; +enableNativeCSSParsing?: () => boolean; +enableNewBackgroundAndBorderDrawables?: () => boolean; +enablePropsUpdateReconciliationAndroid?: () => boolean; From 428a1d1d16059da1e437e91b0709934865debd75 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 09:35:02 -0700 Subject: [PATCH 3/7] Revert uikit proxies Summary: We will use main queue setup of native modules to solve this problem instead. Changelog: [Internal] Differential Revision: D71341038 Reviewed By: mdvacca --- packages/react-native/React/Base/RCTBridge.mm | 2 - .../RCTInitialAccessibilityValuesProxy.h | 30 --- .../RCTInitialAccessibilityValuesProxy.mm | 185 ------------------ .../UIKitProxies/RCTInitializeUIKitProxies.h | 10 - .../UIKitProxies/RCTInitializeUIKitProxies.mm | 26 --- .../UIKitProxies/RCTKeyWindowValuesProxy.h | 23 --- .../UIKitProxies/RCTKeyWindowValuesProxy.mm | 123 ------------ .../UIKitProxies/RCTTraitCollectionProxy.h | 26 --- .../UIKitProxies/RCTTraitCollectionProxy.mm | 80 -------- .../UIKitProxies/RCTWindowSafeAreaProxy.h | 26 --- .../UIKitProxies/RCTWindowSafeAreaProxy.mm | 70 ------- .../CoreModules/RCTAccessibilityManager.mm | 20 +- .../React/CoreModules/RCTAppearance.mm | 10 +- .../React/CoreModules/RCTDeviceInfo.mm | 63 ++++-- .../React/CoreModules/RCTPlatform.mm | 40 ++-- .../platform/ios/ReactCommon/RCTHost.mm | 2 - 16 files changed, 81 insertions(+), 655 deletions(-) delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.h delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.mm delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h delete mode 100644 packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm diff --git a/packages/react-native/React/Base/RCTBridge.mm b/packages/react-native/React/Base/RCTBridge.mm index 7d26bf59bcd9..32000177fcb5 100644 --- a/packages/react-native/React/Base/RCTBridge.mm +++ b/packages/react-native/React/Base/RCTBridge.mm @@ -15,7 +15,6 @@ #if RCT_ENABLE_INSPECTOR #import "RCTInspectorDevServerHelper.h" #endif -#import #import #import #import @@ -512,7 +511,6 @@ - (void)setUp _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString]; RCTExecuteOnMainQueue(^{ - RCTInitializeUIKitProxies(); RCTRegisterReloadCommandListener(self); RCTReloadCommandSetBundleURL(self->_bundleURL); }); diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.h deleted file mode 100644 index ae369bf68730..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.h +++ /dev/null @@ -1,30 +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. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface RCTInitialAccessibilityValuesProxy : NSObject - -+ (instancetype)sharedInstance; - -@property (readonly, nonatomic) BOOL isBoldTextEnabled; -@property (readonly, nonatomic) BOOL isGrayscaleEnabled; -@property (readonly, nonatomic) BOOL isInvertColorsEnabled; -@property (readonly, nonatomic) BOOL isReduceMotionEnabled; -@property (readonly, nonatomic) BOOL isDarkerSystemColorsEnabled; -@property (readonly, nonatomic) BOOL prefersCrossFadeTransitions; -@property (readonly, nonatomic) BOOL isReduceTransparencyEnabled; -@property (readonly, nonatomic) BOOL isVoiceOverEnabled; -@property (readonly, nonatomic) UIContentSizeCategory preferredContentSizeCategory; - -- (void)recordAccessibilityValues; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.mm deleted file mode 100644 index b13b049f5cf0..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTInitialAccessibilityValuesProxy.mm +++ /dev/null @@ -1,185 +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. - */ - -#import "RCTInitialAccessibilityValuesProxy.h" -#import -#import - -@implementation RCTInitialAccessibilityValuesProxy { - BOOL _hasRecordedInitialAccessibilityValues; - BOOL _isBoldTextEnabled; - BOOL _isGrayscaleEnabled; - BOOL _isInvertColorsEnabled; - BOOL _isReduceMotionEnabled; - BOOL _isDarkerSystemColorsEnabled; - BOOL _isReduceTransparencyEnabled; - BOOL _isVoiceOverEnabled; - UIContentSizeCategory _preferredContentSizeCategory; - std::mutex _mutex; -} - -+ (instancetype)sharedInstance -{ - static RCTInitialAccessibilityValuesProxy *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [RCTInitialAccessibilityValuesProxy new]; - }); - return sharedInstance; -} - -- (BOOL)isBoldTextEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isBoldTextEnabled; - } - } - - __block BOOL isBoldTextEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isBoldTextEnabled = UIAccessibilityIsBoldTextEnabled(); - }); - - return isBoldTextEnabled; -} - -- (BOOL)isGrayscaleEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isGrayscaleEnabled; - } - } - - __block BOOL isGrayscaleEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isGrayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); - }); - - return isGrayscaleEnabled; -} - -- (BOOL)isInvertColorsEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isInvertColorsEnabled; - } - } - - __block BOOL isInvertColorsEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isInvertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); - }); - - return isInvertColorsEnabled; -} - -- (BOOL)isReduceMotionEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isReduceMotionEnabled; - } - } - - __block BOOL isReduceMotionEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isReduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled(); - }); - - return isReduceMotionEnabled; -} - -- (BOOL)isDarkerSystemColorsEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isDarkerSystemColorsEnabled; - } - } - - __block BOOL isDarkerSystemColorsEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isDarkerSystemColorsEnabled = UIAccessibilityDarkerSystemColorsEnabled(); - }); - - return isDarkerSystemColorsEnabled; -} - -- (BOOL)isReduceTransparencyEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isReduceTransparencyEnabled; - } - } - - __block BOOL isReduceTransparencyEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isReduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); - }); - - return isReduceTransparencyEnabled; -} - -- (BOOL)isVoiceOverEnabled -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _isVoiceOverEnabled; - } - } - - __block BOOL isVoiceOverEnabled; - RCTUnsafeExecuteOnMainQueueSync(^{ - isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); - }); - - return isVoiceOverEnabled; -} - -- (UIContentSizeCategory)preferredContentSizeCategory -{ - { - std::lock_guard lock(_mutex); - if (_hasRecordedInitialAccessibilityValues) { - return _preferredContentSizeCategory; - } - } - - __block UIContentSizeCategory preferredContentSizeCategory; - RCTUnsafeExecuteOnMainQueueSync(^{ - preferredContentSizeCategory = RCTSharedApplication().preferredContentSizeCategory; - }); - - return preferredContentSizeCategory; -} - -- (void)recordAccessibilityValues -{ - std::lock_guard lock(_mutex); - _hasRecordedInitialAccessibilityValues = YES; - _isBoldTextEnabled = UIAccessibilityIsBoldTextEnabled(); - _isGrayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); - _isInvertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); - _isReduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled(); - _isDarkerSystemColorsEnabled = UIAccessibilityDarkerSystemColorsEnabled(); - _isReduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); - _isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); - _preferredContentSizeCategory = RCTSharedApplication().preferredContentSizeCategory; -} - -@end diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h deleted file mode 100644 index c9f0dbf3585d..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h +++ /dev/null @@ -1,10 +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. - */ - -#import - -void RCTInitializeUIKitProxies(void); diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm deleted file mode 100644 index 6208f2051c06..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm +++ /dev/null @@ -1,26 +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. - */ - -#import "RCTInitializeUIKitProxies.h" -#import -#import "RCTInitialAccessibilityValuesProxy.h" -#import "RCTKeyWindowValuesProxy.h" -#import "RCTTraitCollectionProxy.h" -#import "RCTWindowSafeAreaProxy.h" - -void RCTInitializeUIKitProxies(void) -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [[RCTWindowSafeAreaProxy sharedInstance] startObservingSafeArea]; - [[RCTTraitCollectionProxy sharedInstance] startObservingTraitCollection]; - [[RCTInitialAccessibilityValuesProxy sharedInstance] recordAccessibilityValues]; - [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary]; - // Early cache size of screen to avoid main thread contention - RCTScreenSize(); - }); -} diff --git a/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h deleted file mode 100644 index 0723389c5b0c..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h +++ /dev/null @@ -1,23 +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. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface RCTKeyWindowValuesProxy : NSObject - -+ (instancetype)sharedInstance; - -@property (assign, readonly) CGSize windowSize; -@property (assign, readonly) UIInterfaceOrientation currentInterfaceOrientation; - -- (void)startObservingWindowSizeIfNecessary; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm deleted file mode 100644 index bbf118c8a1ac..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm +++ /dev/null @@ -1,123 +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. - */ - -#import "RCTKeyWindowValuesProxy.h" -#import -#import -#import - -#import - -static NSString *const kFrameKeyPath = @"frame"; - -@implementation RCTKeyWindowValuesProxy { - BOOL _isObserving; - std::mutex _mutex; - CGSize _currentWindowSize; - UIInterfaceOrientation _currentInterfaceOrientation; -} - -+ (instancetype)sharedInstance -{ - static RCTKeyWindowValuesProxy *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [RCTKeyWindowValuesProxy new]; - }); - return sharedInstance; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _isObserving = NO; - UIView *mainWindow = RCTKeyWindow(); - _currentWindowSize = mainWindow ? mainWindow.bounds.size : UIScreen.mainScreen.bounds.size; - } - return self; -} - -- (void)startObservingWindowSizeIfNecessary -{ - // Accesing _isObserving must be done under the lock to avoid a race condition. - // We can't hold the lock while calling RCTUnsafeExecuteOnMainQueueSync. - // Therefore, reading/writing _isObserving is kept separate from calling RCTUnsafeExecuteOnMainQueueSync. - { - std::lock_guard lock(_mutex); - if (_isObserving) { - return; - } - _isObserving = YES; - } - - // For backwards compatibility, we register for notifications from the main thread only. - // On the new architecture, we are already on the main thread and RCTUnsafeExecuteOnMainQueueSync will simply call - // the block. - RCTUnsafeExecuteOnMainQueueSync(^{ - [RCTKeyWindow() addObserver:self forKeyPath:kFrameKeyPath options:NSKeyValueObservingOptionNew context:nil]; - }); - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_interfaceOrientationDidChange) - name:UIApplicationDidBecomeActiveNotification - object:nil]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -{ - if ([keyPath isEqualToString:kFrameKeyPath]) { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self]; - { - std::lock_guard lock(_mutex); - _currentWindowSize = RCTKeyWindow().bounds.size; - } - } -} - -- (CGSize)windowSize -{ - { - std::lock_guard lock(_mutex); - if (_isObserving) { - return _currentWindowSize; - } - } - - __block CGSize size; - RCTUnsafeExecuteOnMainQueueSync(^{ - size = RCTKeyWindow().bounds.size; - }); - return size; -} - -- (UIInterfaceOrientation)currentInterfaceOrientation -{ - { - std::lock_guard lock(_mutex); - if (_isObserving) { - return _currentInterfaceOrientation; - } - } - - __block UIInterfaceOrientation interfaceOrientation; - RCTUnsafeExecuteOnMainQueueSync(^{ - interfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; - }); - return interfaceOrientation; -} - -- (void)_interfaceOrientationDidChange -{ - std::lock_guard lock(_mutex); - _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; -} - -@end diff --git a/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h deleted file mode 100644 index a92d9c5eb506..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h +++ /dev/null @@ -1,26 +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. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface RCTTraitCollectionProxy : NSObject - -+ (instancetype)sharedInstance; - -/* - * Property to access the current trait collection. - * Thread safe. - */ -@property (nonatomic, readonly) UITraitCollection *currentTraitCollection; - -- (void)startObservingTraitCollection; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm deleted file mode 100644 index 69cf8aef11be..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm +++ /dev/null @@ -1,80 +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. - */ - -#import "RCTTraitCollectionProxy.h" -#import -#import - -#import - -@implementation RCTTraitCollectionProxy { - BOOL _isObserving; - std::mutex _mutex; - UITraitCollection *_currentTraitCollection; -} - -+ (instancetype)sharedInstance -{ - static RCTTraitCollectionProxy *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [RCTTraitCollectionProxy new]; - }); - return sharedInstance; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _isObserving = NO; - _currentTraitCollection = [RCTKeyWindow().traitCollection copy]; - } - return self; -} - -- (void)startObservingTraitCollection -{ - RCTAssertMainQueue(); - std::lock_guard lock(_mutex); - if (!_isObserving) { - _isObserving = YES; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_appearanceDidChange:) - name:RCTUserInterfaceStyleDidChangeNotification - object:nil]; - } -} - -- (UITraitCollection *)currentTraitCollection -{ - { - std::lock_guard lock(_mutex); - if (_isObserving) { - return _currentTraitCollection; - } - } - - // Fallback in case [RCTTraitCollectionProxy startObservingTraitCollection] was not called. - __block UITraitCollection *traitCollection = nil; - RCTUnsafeExecuteOnMainQueueSync(^{ - traitCollection = [RCTKeyWindow().traitCollection copy]; - }); - return traitCollection; -} - -- (void)_appearanceDidChange:(NSNotification *)notification -{ - std::lock_guard lock(_mutex); - - NSDictionary *userInfo = [notification userInfo]; - if (userInfo) { - _currentTraitCollection = userInfo[RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey]; - } -} - -@end diff --git a/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h deleted file mode 100644 index 043ef4271ffa..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h +++ /dev/null @@ -1,26 +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. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface RCTWindowSafeAreaProxy : NSObject - -+ (instancetype)sharedInstance; - -/* - * Property to access the current safe area insets of the window, read-only. - * Thread safe. - */ -@property (nonatomic, readonly) UIEdgeInsets currentSafeAreaInsets; - -- (void)startObservingSafeArea; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm deleted file mode 100644 index 440514a5088a..000000000000 --- a/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm +++ /dev/null @@ -1,70 +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. - */ - -#import "RCTWindowSafeAreaProxy.h" -#import -#import -#import - -#import - -@implementation RCTWindowSafeAreaProxy { - BOOL _isObserving; - std::mutex _mutex; - UIEdgeInsets _currentSafeAreaInsets; -} - -+ (instancetype)sharedInstance -{ - static RCTWindowSafeAreaProxy *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [RCTWindowSafeAreaProxy new]; - }); - return sharedInstance; -} - -- (void)startObservingSafeArea -{ - RCTAssertMainQueue(); - std::lock_guard lock(_mutex); - if (!_isObserving) { - _isObserving = YES; - _currentSafeAreaInsets = RCTKeyWindow().safeAreaInsets; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_interfaceFrameDidChange) - name:RCTUserInterfaceStyleDidChangeNotification - object:nil]; - } -} - -- (UIEdgeInsets)currentSafeAreaInsets -{ - { - std::lock_guard lock(_mutex); - if (_isObserving) { - return _currentSafeAreaInsets; - } - } - - // Fallback in case [startObservingSafeArea startObservingSafeArea] was not called. - __block UIEdgeInsets insets; -#if !TARGET_OS_MACCATALYST - RCTUnsafeExecuteOnMainQueueSync(^{ - insets = [UIApplication sharedApplication].delegate.window.safeAreaInsets; - }); -#endif - return insets; -} - -- (void)_interfaceFrameDidChange -{ - std::lock_guard lock(_mutex); - _currentSafeAreaInsets = RCTKeyWindow().safeAreaInsets; -} - -@end diff --git a/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm b/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm index 6158f4a24261..c32a12d57a77 100644 --- a/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm +++ b/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm @@ -12,7 +12,6 @@ #import #import #import -#import #import #import @@ -38,7 +37,7 @@ @implementation RCTAccessibilityManager + (BOOL)requiresMainQueueSetup { - return NO; + return YES; } - (instancetype)init @@ -92,15 +91,14 @@ - (instancetype)init name:UIAccessibilityVoiceOverStatusDidChangeNotification object:nil]; - RCTInitialAccessibilityValuesProxy *initialValuesProxy = [RCTInitialAccessibilityValuesProxy sharedInstance]; - self.contentSizeCategory = initialValuesProxy.preferredContentSizeCategory; - _isBoldTextEnabled = initialValuesProxy.isBoldTextEnabled; - _isGrayscaleEnabled = initialValuesProxy.isGrayscaleEnabled; - _isInvertColorsEnabled = initialValuesProxy.isInvertColorsEnabled; - _isReduceMotionEnabled = initialValuesProxy.isReduceMotionEnabled; - _isDarkerSystemColorsEnabled = initialValuesProxy.isDarkerSystemColorsEnabled; - _isReduceTransparencyEnabled = initialValuesProxy.isReduceTransparencyEnabled; - _isVoiceOverEnabled = initialValuesProxy.isVoiceOverEnabled; + self.contentSizeCategory = RCTSharedApplication().preferredContentSizeCategory; + _isBoldTextEnabled = UIAccessibilityIsBoldTextEnabled(); + _isGrayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); + _isInvertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); + _isReduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled(); + _isDarkerSystemColorsEnabled = UIAccessibilityDarkerSystemColorsEnabled(); + _isReduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); + _isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); } return self; } diff --git a/packages/react-native/React/CoreModules/RCTAppearance.mm b/packages/react-native/React/CoreModules/RCTAppearance.mm index e9419d89315e..67f3eec8c70b 100644 --- a/packages/react-native/React/CoreModules/RCTAppearance.mm +++ b/packages/react-native/React/CoreModules/RCTAppearance.mm @@ -10,7 +10,6 @@ #import #import #import -#import #import "CoreModulesPlugins.h" @@ -90,7 +89,7 @@ @implementation RCTAppearance { - (instancetype)init { if ((self = [super init])) { - UITraitCollection *traitCollection = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection; + UITraitCollection *traitCollection = RCTKeyWindow().traitCollection; _currentColorScheme = RCTColorSchemePreference(traitCollection); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appearanceChanged:) @@ -104,7 +103,7 @@ - (instancetype)init + (BOOL)requiresMainQueueSetup { - return NO; + return YES; } - (dispatch_queue_t)methodQueue @@ -133,7 +132,10 @@ - (dispatch_queue_t)methodQueue RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getColorScheme) { if (!sIsAppearancePreferenceSet) { - UITraitCollection *traitCollection = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection; + __block UITraitCollection *traitCollection = nil; + RCTUnsafeExecuteOnMainQueueSync(^{ + traitCollection = RCTKeyWindow().traitCollection; + }); _currentColorScheme = RCTColorSchemePreference(traitCollection); } return _currentColorScheme; diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index 6b4fcef85225..193e97448dea 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -14,9 +14,7 @@ #import #import #import -#import #import -#import #import #import "CoreModulesPlugins.h" @@ -33,6 +31,8 @@ @implementation RCTDeviceInfo { std::atomic _invalidated; } +static NSString *const kFrameKeyPath = @"frame"; + @synthesize moduleRegistry = _moduleRegistry; RCT_EXPORT_MODULE() @@ -40,14 +40,25 @@ @implementation RCTDeviceInfo { - (instancetype)init { if (self = [super init]) { - [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary]; + [RCTKeyWindow() addObserver:self forKeyPath:kFrameKeyPath options:NSKeyValueObservingOptionNew context:nil]; } return self; } +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + if ([keyPath isEqualToString:kFrameKeyPath]) { + [self interfaceFrameDidChange]; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self]; + } +} + + (BOOL)requiresMainQueueSetup { - return NO; + return YES; } - (dispatch_queue_t)methodQueue @@ -81,7 +92,7 @@ - (void)initialize #if TARGET_OS_IOS - _currentInterfaceOrientation = [RCTKeyWindowValuesProxy sharedInstance].currentInterfaceOrientation; + _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interfaceFrameDidChange) @@ -120,6 +131,8 @@ - (void)_cleanupObservers [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTBridgeWillInvalidateModulesNotification object:nil]; + [RCTKeyWindow() removeObserver:self forKeyPath:kFrameKeyPath]; + #if TARGET_OS_IOS [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; #endif @@ -132,8 +145,13 @@ static BOOL RCTIsIPhoneNotched() #if TARGET_OS_IOS dispatch_once(&onceToken, ^{ + RCTAssertMainQueue(); + // 20pt is the top safeArea value in non-notched devices - isIPhoneNotched = [RCTWindowSafeAreaProxy sharedInstance].currentSafeAreaInsets.top > 20; + UIWindow *keyWindow = RCTKeyWindow(); + if (keyWindow) { + isIPhoneNotched = keyWindow.safeAreaInsets.top > 20; + } }); #endif @@ -142,11 +160,13 @@ static BOOL RCTIsIPhoneNotched() static NSDictionary *RCTExportedDimensions(CGFloat fontScale) { + RCTAssertMainQueue(); UIScreen *mainScreen = UIScreen.mainScreen; CGSize screenSize = mainScreen.bounds.size; + UIView *mainWindow = RCTKeyWindow(); // We fallback to screen size if a key window is not found. - CGSize windowSize = [RCTKeyWindowValuesProxy sharedInstance].windowSize; + CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize; NSDictionary *dimsWindow = @{ @"width" : @(windowSize.width), @@ -182,14 +202,20 @@ - (NSDictionary *)_exportedDimensions - (NSDictionary *)getConstants { - return @{ - @"Dimensions" : [self _exportedDimensions], - // Note: - // This prop is deprecated and will be removed in a future release. - // Please use this only for a quick and temporary solution. - // Use instead. - @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()), - }; + __block NSDictionary *constants; + __weak __typeof(self) weakSelf = self; + RCTUnsafeExecuteOnMainQueueSync(^{ + constants = @{ + @"Dimensions" : [weakSelf _exportedDimensions], + // Note: + // This prop is deprecated and will be removed in a future release. + // Please use this only for a quick and temporary solution. + // Use instead. + @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()), + }; + }); + + return constants; } - (void)didReceiveNewContentSizeMultiplier @@ -209,10 +235,11 @@ - (void)didReceiveNewContentSizeMultiplier - (void)interfaceOrientationDidChange { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - UIWindow *keyWindow = RCTKeyWindow(); - UIInterfaceOrientation nextOrientation = keyWindow.windowScene.interfaceOrientation; + UIApplication *application = RCTSharedApplication(); + UIInterfaceOrientation nextOrientation = RCTKeyWindow().windowScene.interfaceOrientation; - BOOL isRunningInFullScreen = CGRectEqualToRect(keyWindow.frame, keyWindow.screen.bounds); + BOOL isRunningInFullScreen = + CGRectEqualToRect(application.delegate.window.frame, application.delegate.window.screen.bounds); // 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-> diff --git a/packages/react-native/React/CoreModules/RCTPlatform.mm b/packages/react-native/React/CoreModules/RCTPlatform.mm index 8ae71b130072..ec8a5f3220e3 100644 --- a/packages/react-native/React/CoreModules/RCTPlatform.mm +++ b/packages/react-native/React/CoreModules/RCTPlatform.mm @@ -10,7 +10,6 @@ #import #import -#import #import #import @@ -49,7 +48,7 @@ @implementation RCTPlatform + (BOOL)requiresMainQueueSetup { - return NO; + return YES; } - (dispatch_queue_t)methodQueue @@ -65,27 +64,30 @@ - (dispatch_queue_t)methodQueue - (ModuleConstants)getConstants { - UIDevice *device = [UIDevice currentDevice]; - bool isForceTouchAvailable = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection.forceTouchCapability == - UIForceTouchCapabilityAvailable; - auto versions = RCTGetReactNativeVersion(); - return typedConstants({ - .forceTouchAvailable = isForceTouchAvailable, - .osVersion = [device systemVersion], - .systemName = [device systemName], - .interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]), - .isTesting = RCTRunningInTestEnvironment() ? true : false, - .reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder( - {.minor = [versions[@"minor"] doubleValue], - .major = [versions[@"major"] doubleValue], - .patch = [versions[@"patch"] doubleValue], - .prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]] ? nullptr : versions[@"prerelease"]}), + __block ModuleConstants constants; + RCTUnsafeExecuteOnMainQueueSync(^{ + UIDevice *device = [UIDevice currentDevice]; + auto versions = RCTGetReactNativeVersion(); + constants = typedConstants({ + .forceTouchAvailable = RCTForceTouchAvailable() ? true : false, + .osVersion = [device systemVersion], + .systemName = [device systemName], + .interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]), + .isTesting = RCTRunningInTestEnvironment() ? true : false, + .reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder( + {.minor = [versions[@"minor"] doubleValue], + .major = [versions[@"major"] doubleValue], + .patch = [versions[@"patch"] doubleValue], + .prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]] ? nullptr : versions[@"prerelease"]}), #if TARGET_OS_MACCATALYST - .isMacCatalyst = true, + .isMacCatalyst = true, #else - .isMacCatalyst = false, + .isMacCatalyst = false, #endif + }); }); + + return constants; } - (std::shared_ptr)getTurboModule:(const ObjCTurboModule::InitParams &)params diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index d0a4e7c5ec80..e9ca6925ca48 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -12,7 +12,6 @@ #import #import #import -#import #import #import #import @@ -194,7 +193,6 @@ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider RCTExecuteOnMainQueue(^{ // Listen to reload commands RCTRegisterReloadCommandListener(self); - RCTInitializeUIKitProxies(); }); _inspectorHostDelegate = std::make_unique(self); From 361d80b40d7938289def7d781af5592454389394 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 09:58:46 -0700 Subject: [PATCH 4/7] Make PlatformConstants use main queue setup (#50111) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50111 ## Rationale Rendering can now include main -> js sync calls. If we allow js -> main sync calls during rendering, react native can deadlock. So, this diff moves the js -> main sync calls to "main queue module setup", which occurs before rendering. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D71480047 --- .../React/CoreModules/RCTPlatform.mm | 57 ++++++++++--------- packages/react-native/package.json | 8 ++- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/packages/react-native/React/CoreModules/RCTPlatform.mm b/packages/react-native/React/CoreModules/RCTPlatform.mm index ec8a5f3220e3..2aa4e28be8e7 100644 --- a/packages/react-native/React/CoreModules/RCTPlatform.mm +++ b/packages/react-native/React/CoreModules/RCTPlatform.mm @@ -10,6 +10,7 @@ #import #import +#import #import #import @@ -39,10 +40,12 @@ } } -@interface RCTPlatform () +@interface RCTPlatform () @end -@implementation RCTPlatform +@implementation RCTPlatform { + ModuleConstants _constants; +} RCT_EXPORT_MODULE(PlatformConstants) @@ -51,6 +54,29 @@ + (BOOL)requiresMainQueueSetup return YES; } +- (void)initialize +{ + UIDevice *device = [UIDevice currentDevice]; + auto versions = RCTGetReactNativeVersion(); + _constants = typedConstants({ + .forceTouchAvailable = RCTForceTouchAvailable() ? true : false, + .osVersion = [device systemVersion], + .systemName = [device systemName], + .interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]), + .isTesting = RCTRunningInTestEnvironment() ? true : false, + .reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder( + {.minor = [versions[@"minor"] doubleValue], + .major = [versions[@"major"] doubleValue], + .patch = [versions[@"patch"] doubleValue], + .prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]] ? nullptr : versions[@"prerelease"]}), +#if TARGET_OS_MACCATALYST + .isMacCatalyst = true, +#else + .isMacCatalyst = false, +#endif + }); +} + - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -59,35 +85,12 @@ - (dispatch_queue_t)methodQueue // TODO: Use the generated struct return type. - (ModuleConstants)constantsToExport { - return (ModuleConstants)[self getConstants]; + return _constants; } - (ModuleConstants)getConstants { - __block ModuleConstants constants; - RCTUnsafeExecuteOnMainQueueSync(^{ - UIDevice *device = [UIDevice currentDevice]; - auto versions = RCTGetReactNativeVersion(); - constants = typedConstants({ - .forceTouchAvailable = RCTForceTouchAvailable() ? true : false, - .osVersion = [device systemVersion], - .systemName = [device systemName], - .interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]), - .isTesting = RCTRunningInTestEnvironment() ? true : false, - .reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder( - {.minor = [versions[@"minor"] doubleValue], - .major = [versions[@"major"] doubleValue], - .patch = [versions[@"patch"] doubleValue], - .prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]] ? nullptr : versions[@"prerelease"]}), -#if TARGET_OS_MACCATALYST - .isMacCatalyst = true, -#else - .isMacCatalyst = false, -#endif - }); - }); - - return constants; + return _constants; } - (std::shared_ptr)getTurboModule:(const ObjCTurboModule::InitParams &)params diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 6fdcfa480495..63ca1cb983d0 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -149,7 +149,13 @@ { "name": "FBReactNativeSpec", "type": "modules", - "ios": {}, + "ios": { + "modules": { + "PlatformConstants": { + "unstableRequiresMainQueueSetup": true + } + } + }, "android": {}, "jsSrcsDir": "src" }, From 401d2c49352d045a6d105add96b889b00bf22216 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 09:58:46 -0700 Subject: [PATCH 5/7] Make DeviceInfo use main queue setup (#50109) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50109 ## Rationale Rendering can now include main -> js sync calls. If we allow js -> main sync calls during rendering, react native can deadlock. So, this diff moves the js -> main sync calls to "main queue module setup", which occurs before rendering. Changelog: [Internal] Differential Revision: D71347449 Reviewed By: mdvacca --- .../React/CoreModules/RCTDeviceInfo.mm | 25 ++++++++----------- packages/react-native/package.json | 3 +++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index 193e97448dea..8410dcefed9d 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -29,6 +29,7 @@ @implementation RCTDeviceInfo { NSDictionary *_currentInterfaceDimensions; BOOL _isFullscreen; std::atomic _invalidated; + NSDictionary *_constants; } static NSString *const kFrameKeyPath = @"frame"; @@ -109,6 +110,15 @@ - (void)initialize selector:@selector(invalidate) name:RCTBridgeWillInvalidateModulesNotification object:nil]; + + _constants = @{ + @"Dimensions" : [self _exportedDimensions], + // Note: + // This prop is deprecated and will be removed in a future release. + // Please use this only for a quick and temporary solution. + // Use instead. + @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()), + }; } - (void)invalidate @@ -202,20 +212,7 @@ - (NSDictionary *)_exportedDimensions - (NSDictionary *)getConstants { - __block NSDictionary *constants; - __weak __typeof(self) weakSelf = self; - RCTUnsafeExecuteOnMainQueueSync(^{ - constants = @{ - @"Dimensions" : [weakSelf _exportedDimensions], - // Note: - // This prop is deprecated and will be removed in a future release. - // Please use this only for a quick and temporary solution. - // Use instead. - @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()), - }; - }); - - return constants; + return _constants; } - (void)didReceiveNewContentSizeMultiplier diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 63ca1cb983d0..e824a56e10eb 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -151,6 +151,9 @@ "type": "modules", "ios": { "modules": { + "DeviceInfo": { + "unstableRequiresMainQueueSetup": true + }, "PlatformConstants": { "unstableRequiresMainQueueSetup": true } From ea0a0fd989d1272121df3aab7be31f2269b0316c Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 14:28:12 -0700 Subject: [PATCH 6/7] Make AccessibilityManager use main queue setup (#50112) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50112 ## Rationale Rendering can now include main -> js sync calls. If we allow js -> main sync calls during rendering, react native can deadlock. So, this diff moves the js -> main sync calls to "main queue module setup", which occurs before rendering. Changelog: [Internal] Differential Revision: D71347448 Reviewed By: mdvacca --- packages/react-native/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index e824a56e10eb..cd2387499eb9 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -151,6 +151,9 @@ "type": "modules", "ios": { "modules": { + "AccessibilityManager": { + "unstableRequiresMainQueueSetup": true + }, "DeviceInfo": { "unstableRequiresMainQueueSetup": true }, From aec510b04bda53c432cc45129e3309d550bd1026 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Wed, 19 Mar 2025 14:54:47 -0700 Subject: [PATCH 7/7] Make StatusBarManager use main queue setup (#50113) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50113 ## Rationale Rendering can now include main -> js sync calls. If we allow js -> main sync calls during rendering, react native can deadlock. So, this diff moves the js -> main sync calls to "main queue module setup", which occurs before rendering. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D71348559 --- .../PlatformStubs/RCTStatusBarManager.mm | 11 +++----- .../React/CoreModules/RCTStatusBarManager.mm | 25 +++++++++++-------- packages/react-native/package.json | 3 +++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/react-native/React/CoreModules/PlatformStubs/RCTStatusBarManager.mm b/packages/react-native/React/CoreModules/PlatformStubs/RCTStatusBarManager.mm index 39efae8d4fb1..287128e83dfa 100644 --- a/packages/react-native/React/CoreModules/PlatformStubs/RCTStatusBarManager.mm +++ b/packages/react-native/React/CoreModules/PlatformStubs/RCTStatusBarManager.mm @@ -27,15 +27,10 @@ @implementation RCTStatusBarManager - (facebook::react::ModuleConstants)getConstants { - __block facebook::react::ModuleConstants constants; - RCTUnsafeExecuteOnMainQueueSync(^{ - constants = facebook::react::typedConstants({ - .HEIGHT = 0, - .DEFAULT_BACKGROUND_COLOR = std::nullopt, - }); + return facebook::react::typedConstants({ + .HEIGHT = 0, + .DEFAULT_BACKGROUND_COLOR = std::nullopt, }); - - return constants; } - (facebook::react::ModuleConstants)constantsToExport diff --git a/packages/react-native/React/CoreModules/RCTStatusBarManager.mm b/packages/react-native/React/CoreModules/RCTStatusBarManager.mm index 5762334cecb0..a7cd2cc9db5e 100644 --- a/packages/react-native/React/CoreModules/RCTStatusBarManager.mm +++ b/packages/react-native/React/CoreModules/RCTStatusBarManager.mm @@ -9,6 +9,7 @@ #import "CoreModulesPlugins.h" #import +#import #import #import @@ -47,10 +48,12 @@ + (UIStatusBarStyle)UIStatusBarStyle:(id)json RCT_DYNAMIC @end -@interface RCTStatusBarManager () +@interface RCTStatusBarManager () @end -@implementation RCTStatusBarManager +@implementation RCTStatusBarManager { + facebook::react::ModuleConstants _constants; +} static BOOL RCTViewControllerBasedStatusBarAppearance() { @@ -72,6 +75,14 @@ + (BOOL)requiresMainQueueSetup return YES; } +- (void)initialize +{ + _constants = facebook::react::typedConstants({ + .HEIGHT = RCTUIStatusBarManager().statusBarFrame.size.height, + .DEFAULT_BACKGROUND_COLOR = std::nullopt, + }); +} + - (NSArray *)supportedEvents { return @[ kStatusBarFrameDidChange, kStatusBarFrameWillChange ]; @@ -177,15 +188,7 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification - (facebook::react::ModuleConstants)getConstants { - __block facebook::react::ModuleConstants constants; - RCTUnsafeExecuteOnMainQueueSync(^{ - constants = facebook::react::typedConstants({ - .HEIGHT = RCTUIStatusBarManager().statusBarFrame.size.height, - .DEFAULT_BACKGROUND_COLOR = std::nullopt, - }); - }); - - return constants; + return _constants; } - (facebook::react::ModuleConstants)constantsToExport diff --git a/packages/react-native/package.json b/packages/react-native/package.json index cd2387499eb9..9cc59bc22f21 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -159,6 +159,9 @@ }, "PlatformConstants": { "unstableRequiresMainQueueSetup": true + }, + "StatusBarManager": { + "unstableRequiresMainQueueSetup": true } } },