diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h index a8b2ab8c6e57b1..455d23e42cb525 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h @@ -24,6 +24,7 @@ @class RCTBridge; @protocol RCTComponentViewProtocol; @class RCTSurfacePresenterBridgeAdapter; +@class RCTDevMenuConfiguration; NS_ASSUME_NONNULL_BEGIN @@ -116,6 +117,8 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable }; @property (nonatomic, weak) id delegate; +@property (nonatomic, nullable) RCTDevMenuConfiguration *devMenuConfiguration; + @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm index 9d5a45787af022..5851645cf91f4b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm @@ -7,6 +7,7 @@ #import "RCTReactNativeFactory.h" #import +#import #import #import #import @@ -82,7 +83,8 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName { UIView *rootView = [self.rootViewFactory viewWithModuleName:moduleName initialProperties:initialProperties - launchOptions:launchOptions]; + launchOptions:launchOptions + devMenuConfiguration:self.devMenuConfiguration]; UIViewController *rootViewController = [_delegate createRootViewController]; [_delegate setRootView:rootView toRootViewController:rootViewController]; window.rootViewController = rootViewController; diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h index 35403dac84a72f..119d91a9ee29fc 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h @@ -18,6 +18,7 @@ @class RCTHost; @class RCTRootView; @class RCTSurfacePresenterBridgeAdapter; +@class RCTDevMenuConfiguration; NS_ASSUME_NONNULL_BEGIN @@ -201,7 +202,13 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc * @parameter: moduleName - the name of the app, used by Metro to resolve the module. * @parameter: initialProperties - a set of initial properties. * @parameter: launchOptions - a dictionary with a set of options. + * @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu. */ +- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName + initialProperties:(NSDictionary *__nullable)initialProperties + launchOptions:(NSDictionary *__nullable)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration; + - (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *__nullable)initialProperties launchOptions:(NSDictionary *__nullable)launchOptions; @@ -219,11 +226,16 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc * Use it to speed up later viewWithModuleName: calls. * * @parameter: launchOptions - a dictionary with a set of options. + * @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu. */ -- (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions; +- (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration; - (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions; +- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration; + @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index a77797a9bddb5a..77201b675ec53b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -7,6 +7,7 @@ #import "RCTRootViewFactory.h" #import +#import #import #import #import @@ -133,29 +134,47 @@ - (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configu - (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties { - return [self viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:nil]; + return [self viewWithModuleName:moduleName + initialProperties:initialProperties + launchOptions:nil + devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } - (UIView *)viewWithModuleName:(NSString *)moduleName { - return [self viewWithModuleName:moduleName initialProperties:nil launchOptions:nil]; + return [self viewWithModuleName:moduleName + initialProperties:nil + launchOptions:nil + devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } - (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { // Enable TurboModule interop by default in Bridgeless mode RCTEnableTurboModuleInterop(YES); RCTEnableTurboModuleInteropBridgeProxy(YES); - [self createReactHostIfNeeded:launchOptions]; + [self createReactHostIfNeeded:launchOptions devMenuConfiguration:devMenuConfiguration]; return; } +- (UIView *)viewWithModuleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties + launchOptions:(NSDictionary *)launchOptions +{ + return [self viewWithModuleName:moduleName + initialProperties:initialProperties + launchOptions:launchOptions + devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; +} + - (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)initProps launchOptions:(NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { - [self initializeReactHostWithLaunchOptions:launchOptions]; + [self initializeReactHostWithLaunchOptions:launchOptions devMenuConfiguration:devMenuConfiguration]; RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName initialProperties:initProps ? initProps : @{}]; @@ -226,14 +245,21 @@ - (void)createBridgeAdapterIfNeeded #pragma mark - New Arch Utilities - (void)createReactHostIfNeeded:(NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { if (self.reactHost) { return; } - self.reactHost = [self createReactHost:launchOptions]; + self.reactHost = [self createReactHost:launchOptions devMenuConfiguration:devMenuConfiguration]; +} + +- (RCTHost *)createReactHost:(NSDictionary *)launchOptions +{ + return [self createReactHost:launchOptions devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } - (RCTHost *)createReactHost:(NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { __weak __typeof(self) weakSelf = self; RCTHost *reactHost = @@ -243,7 +269,8 @@ - (RCTHost *)createReactHost:(NSDictionary *)launchOptions jsEngineProvider:^std::shared_ptr() { return [weakSelf createJSRuntimeFactory]; } - launchOptions:launchOptions]; + launchOptions:launchOptions + devMenuConfiguration:devMenuConfiguration]; [reactHost setBundleURLProvider:^NSURL *() { return [weakSelf bundleURL]; }]; diff --git a/packages/react-native/React/Base/RCTBundleURLProvider.mm b/packages/react-native/React/Base/RCTBundleURLProvider.mm index fb1d601c075d8d..89ccd0c7ad7a58 100644 --- a/packages/react-native/React/Base/RCTBundleURLProvider.mm +++ b/packages/react-native/React/Base/RCTBundleURLProvider.mm @@ -18,7 +18,7 @@ const NSUInteger kRCTBundleURLProviderDefaultPort = RCT_METRO_PORT; -#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY +#if RCT_DEV | RCT_PACKAGER_LOADING_FUNCTIONALITY static BOOL kRCTAllowPackagerAccess = YES; void RCTBundleURLProviderAllowPackagerServerAccess(BOOL allowed) { @@ -78,7 +78,7 @@ - (void)resetToDefaults (unsigned long)kRCTBundleURLProviderDefaultPort]]; } -#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY +#if RCT_DEV | RCT_PACKAGER_LOADING_FUNCTIONALITY + (BOOL)isPackagerRunning:(NSString *)hostPort { return [RCTBundleURLProvider isPackagerRunning:hostPort scheme:nil]; @@ -155,14 +155,14 @@ - (NSString *)packagerServerHost - (NSString *)packagerServerHostPort { -#if RCT_DEV_MENU | RCT_PACKAGER_LOADING_FUNCTIONALITY +#if RCT_DEV | RCT_PACKAGER_LOADING_FUNCTIONALITY if (!kRCTAllowPackagerAccess) { RCTLogInfo(@"Packager server access is disabled in this environment"); return nil; } #endif NSString *location = [self jsLocation]; -#if RCT_DEV_MENU +#if RCT_DEV NSString *scheme = [self packagerScheme]; if ([location length] && ![RCTBundleURLProvider isPackagerRunning:location scheme:scheme]) { location = nil; diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.h b/packages/react-native/React/CoreModules/RCTDevMenu.h index 4886ce60173c09..976cb8c517b58e 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.h +++ b/packages/react-native/React/CoreModules/RCTDevMenu.h @@ -12,12 +12,26 @@ #import #import +RCT_EXTERN NSString *const RCTShowDevMenuNotification; + +@interface RCTDevMenuConfiguration : NSObject + #if RCT_DEV_MENU -RCT_EXTERN NSString *const RCTShowDevMenuNotification; +@property (nonatomic, readonly) BOOL devMenuEnabled; +@property (nonatomic, readonly) BOOL shakeGestureEnabled; +@property (nonatomic, readonly) BOOL keyboardShortcutsEnabled; + +- (instancetype)initWithDevMenuEnabled:(BOOL)devMenuEnabled + shakeGestureEnabled:(BOOL)shakeGestureEnabled + keyboardShortcutsEnabled:(BOOL)keyboardShortcutsEnabled; #endif ++ (instancetype)defaultConfiguration; + +@end + @class RCTDevMenuItem; /** @@ -45,6 +59,16 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification; */ @property (nonatomic, assign) BOOL hotkeysEnabled; +/** + * Whether the developer menu is enabled. + */ +@property (nonatomic, assign) BOOL devMenuEnabled; + +/** + * Whether keyboard shortcuts are enabled. + */ +@property (nonatomic, assign) BOOL keyboardShortcutsEnabled; + /** * Presented items in development menu */ @@ -76,6 +100,11 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification; */ - (void)addItem:(RCTDevMenuItem *)item; +/** + * Disable the reload command (Cmd+R) in the simulator. + */ +- (void)disableReloadCommand; + @end typedef NSString * (^RCTDevMenuItemTitleBlock)(void); diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.mm b/packages/react-native/React/CoreModules/RCTDevMenu.mm index d804ce5b0b7225..890c1596df74f7 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.mm +++ b/packages/react-native/React/CoreModules/RCTDevMenu.mm @@ -29,6 +29,27 @@ - (RCTDevMenuItem *)devMenuItem; NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification"; +@implementation RCTDevMenuConfiguration +- (instancetype)initWithDevMenuEnabled:(BOOL)devMenuEnabled + shakeGestureEnabled:(BOOL)shakeGestureEnabled + keyboardShortcutsEnabled:(BOOL)keyboardShortcutsEnabled +{ + if (self = [super init]) { + _devMenuEnabled = devMenuEnabled; + _shakeGestureEnabled = shakeGestureEnabled; + _keyboardShortcutsEnabled = keyboardShortcutsEnabled; + } + return self; +} + ++ (instancetype)defaultConfiguration +{ + return [[self alloc] initWithDevMenuEnabled:RCT_DEV_MENU + shakeGestureEnabled:RCT_DEV_MENU + keyboardShortcutsEnabled:RCT_DEV_MENU]; +} +@end + @implementation UIWindow (RCTDevMenu) - (void)RCT_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event @@ -127,6 +148,8 @@ - (instancetype)init object:nil]; _extraMenuItems = [NSMutableArray new]; + _keyboardShortcutsEnabled = true; + _devMenuEnabled = true; [self registerHotkeys]; } return self; @@ -162,7 +185,6 @@ - (void)unregisterHotkeys [commands unregisterKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand]; [commands unregisterKeyCommandWithInput:@"i" modifierFlags:UIKeyModifierCommand]; - [commands unregisterKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand]; #endif } @@ -172,13 +194,30 @@ - (BOOL)isHotkeysRegistered RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; return [commands isKeyCommandRegisteredForInput:@"d" modifierFlags:UIKeyModifierCommand] && - [commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand] && - [commands isKeyCommandRegisteredForInput:@"n" modifierFlags:UIKeyModifierCommand]; + [commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand]; #else return NO; #endif } +- (BOOL)isReloadCommandRegistered +{ +#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST + RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; + return [commands isKeyCommandRegisteredForInput:@"r" modifierFlags:UIKeyModifierCommand]; +#else + return NO; +#endif +} + +- (void)unregisterReloadCommand +{ +#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST + RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; + [commands unregisterKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand]; +#endif +} + - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -240,6 +279,17 @@ - (void)addItem:(RCTDevMenuItem *)item [_extraMenuItems addObject:item]; } +- (void)setKeyboardShortcutsEnabled:(BOOL)keyboardShortcutsEnabled +{ + if (_keyboardShortcutsEnabled != keyboardShortcutsEnabled) { + [self setHotkeysEnabled:keyboardShortcutsEnabled]; + + if (!keyboardShortcutsEnabled) { + [self disableReloadCommand]; + } + } +} + - (void)setDefaultJSBundle { [[RCTBundleURLProvider sharedSettings] resetToDefaults]; @@ -382,7 +432,7 @@ - (void)setDefaultJSBundle RCT_EXPORT_METHOD(show) { - if ((_actionSheet != nullptr) || RCTRunningInAppExtension()) { + if ((_actionSheet != nullptr) || RCTRunningInAppExtension() || !_devMenuEnabled) { return; } @@ -437,7 +487,7 @@ - (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__ - (void)setShakeToShow:(BOOL)shakeToShow { - ((RCTDevSettings *)[_moduleRegistry moduleForName:"DevSettings"]).isShakeToShowDevMenuEnabled = shakeToShow; + [[_moduleRegistry moduleForName:"DevSettings"] setIsShakeToShowDevMenuEnabled:shakeToShow]; } - (BOOL)shakeToShow @@ -487,6 +537,13 @@ - (BOOL)hotkeysEnabled return [self isHotkeysRegistered]; } +- (void)disableReloadCommand +{ + if ([self isReloadCommandRegistered]) { + [self unregisterReloadCommand]; + } +} + - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { @@ -500,6 +557,15 @@ - (BOOL)hotkeysEnabled @interface RCTDevMenu () @end +@implementation RCTDevMenuConfiguration + ++ (instancetype)defaultConfiguration +{ + return nil; +} + +@end + @implementation RCTDevMenu - (void)show @@ -515,6 +581,10 @@ - (void)addItem:(RCTDevMenu *)item { } +- (void)disableReloadCommand +{ +} + - (BOOL)isActionSheetShown { return NO; diff --git a/packages/react-native/React/CoreModules/RCTDevMenuConfigurationDecorator.h b/packages/react-native/React/CoreModules/RCTDevMenuConfigurationDecorator.h new file mode 100644 index 00000000000000..bea72242666eb7 --- /dev/null +++ b/packages/react-native/React/CoreModules/RCTDevMenuConfigurationDecorator.h @@ -0,0 +1,23 @@ +/* + * 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 + +@class RCTDevMenuConfiguration; + +@interface RCTDevMenuConfigurationDecorator : NSObject + +#if RCT_DEV_MENU + +@property (nonatomic, strong, readonly) RCTDevMenuConfiguration *__nullable devMenuConfiguration; + +- (instancetype)initWithDevMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration; +- (void)decorate:(id)devMenuModule; + +#endif + +@end diff --git a/packages/react-native/React/CoreModules/RCTDevMenuConfigurationDecorator.mm b/packages/react-native/React/CoreModules/RCTDevMenuConfigurationDecorator.mm new file mode 100644 index 00000000000000..83bc772e9de93f --- /dev/null +++ b/packages/react-native/React/CoreModules/RCTDevMenuConfigurationDecorator.mm @@ -0,0 +1,52 @@ +/* + * 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 "RCTDevMenuConfigurationDecorator.h" + +#if RCT_DEV_MENU + +#import +#import + +@implementation RCTDevMenuConfigurationDecorator + +- (instancetype)initWithDevMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration +{ + if (self = [super init]) { + _devMenuConfiguration = devMenuConfiguration; + } + + return self; +} + +- (void)decorate:(id)bridgeModule +{ + if (_devMenuConfiguration == nil) { + return; + } + + if ([bridgeModule isKindOfClass:[RCTDevMenu class]]) { + RCTDevMenu *devMenu = (RCTDevMenu *)bridgeModule; + devMenu.devMenuEnabled = _devMenuConfiguration.devMenuEnabled; + devMenu.keyboardShortcutsEnabled = _devMenuConfiguration.keyboardShortcutsEnabled; + } + + if ([bridgeModule isKindOfClass:[RCTDevSettings class]]) { + RCTDevSettings *devSettings = (RCTDevSettings *)bridgeModule; + devSettings.isShakeGestureEnabled = _devMenuConfiguration.shakeGestureEnabled; + } +} + +@end + +#else + +@implementation RCTDevMenuConfigurationDecorator : NSObject + +@end + +#endif diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.h b/packages/react-native/React/CoreModules/RCTDevSettings.h index c8db73ec9427fd..a4d9c8f9e30328 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.h +++ b/packages/react-native/React/CoreModules/RCTDevSettings.h @@ -65,6 +65,11 @@ */ @property (nonatomic, assign, setter=setHotLoadingEnabled:) BOOL isHotLoadingEnabled; +/** + * Whether shake gesture is enabled. + */ +@property (nonatomic, assign) BOOL isShakeGestureEnabled; + /** * Enables starting of profiling sampler on launch */ diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.mm b/packages/react-native/React/CoreModules/RCTDevSettings.mm index 4b3e4749496270..fbbeec0e703d53 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.mm +++ b/packages/react-native/React/CoreModules/RCTDevSettings.mm @@ -145,6 +145,7 @@ - (instancetype)init }; RCTDevSettingsUserDefaultsDataSource *dataSource = [[RCTDevSettingsUserDefaultsDataSource alloc] initWithDefaultValues:defaultValues]; + _isShakeGestureEnabled = true; return [self initWithDataSource:dataSource]; } @@ -262,6 +263,12 @@ - (id)settingForKey:(NSString *)key return [_dataSource settingForKey:key]; } +- (void)setIsShakeGestureEnabled:(BOOL)isShakeGestureEnabled +{ + _isShakeGestureEnabled = isShakeGestureEnabled; + [self setIsShakeToShowDevMenuEnabled:isShakeGestureEnabled]; +} + - (BOOL)isDeviceDebuggingAvailable { #if RCT_ENABLE_INSPECTOR @@ -305,7 +312,7 @@ - (BOOL)isHotLoadingAvailable - (BOOL)isShakeToShowDevMenuEnabled { - return [[self settingForKey:kRCTDevSettingShakeToShowDevMenu] boolValue]; + return _isShakeGestureEnabled && [[self settingForKey:kRCTDevSettingShakeToShowDevMenu] boolValue]; } RCT_EXPORT_METHOD(setProfilingEnabled : (BOOL)enabled) diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h index 33f3a8f84ec513..fa98b221b9b5fa 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h @@ -19,6 +19,7 @@ @class RCTBridgeProxy; @class RCTTurboModuleManager; +@class RCTDevMenuConfigurationDecorator; @protocol RCTTurboModuleManagerDelegate @@ -73,6 +74,12 @@ delegate:(id)delegate jsInvoker:(std::shared_ptr)jsInvoker; +- (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy + bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator + delegate:(id)delegate + jsInvoker:(std::shared_ptr)jsInvoker + devMenuConfigurationDecorator:(RCTDevMenuConfigurationDecorator *)devMenuConfigurationDecorator; + - (void)installJSBindings:(facebook::jsi::Runtime &)runtime; - (void)invalidate; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm index 5168bbf415d1a7..ac4c5c5c2a0f08 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm @@ -22,6 +22,7 @@ #import #import #import +#import #import #import #import @@ -215,15 +216,17 @@ @implementation RCTTurboModuleManager { RCTBridgeProxy *_bridgeProxy; RCTBridgeModuleDecorator *_bridgeModuleDecorator; + RCTDevMenuConfigurationDecorator *_devMenuConfigurationDecorator; dispatch_queue_t _sharedModuleQueue; } - (instancetype)initWithBridge:(RCTBridge *)bridge - bridgeProxy:(RCTBridgeProxy *)bridgeProxy - bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator - delegate:(id)delegate - jsInvoker:(std::shared_ptr)jsInvoker + bridgeProxy:(RCTBridgeProxy *)bridgeProxy + bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator + delegate:(id)delegate + jsInvoker:(std::shared_ptr)jsInvoker + devMenuConfigurationDecorator:(RCTDevMenuConfigurationDecorator *)devMenuConfigurationDecorator { if (self = [super init]) { _jsInvoker = std::move(jsInvoker); @@ -233,6 +236,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge _bridgeModuleDecorator = bridgeModuleDecorator; _invalidating = false; _sharedModuleQueue = dispatch_queue_create("com.meta.react.turbomodulemanager.queue", DISPATCH_QUEUE_SERIAL); + _devMenuConfigurationDecorator = devMenuConfigurationDecorator; if (RCTTurboModuleInteropEnabled()) { // TODO(T174674274): Implement lazy loading of legacy modules in the new architecture. @@ -273,10 +277,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge jsInvoker:(std::shared_ptr)jsInvoker { return [self initWithBridge:bridge - bridgeProxy:nil - bridgeModuleDecorator:[bridge bridgeModuleDecorator] - delegate:delegate - jsInvoker:jsInvoker]; + bridgeProxy:nil + bridgeModuleDecorator:[bridge bridgeModuleDecorator] + delegate:delegate + jsInvoker:jsInvoker + devMenuConfigurationDecorator:nil]; } - (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy @@ -285,10 +290,25 @@ - (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy jsInvoker:(std::shared_ptr)jsInvoker { return [self initWithBridge:nil - bridgeProxy:bridgeProxy - bridgeModuleDecorator:bridgeModuleDecorator - delegate:delegate - jsInvoker:jsInvoker]; + bridgeProxy:bridgeProxy + bridgeModuleDecorator:bridgeModuleDecorator + delegate:delegate + jsInvoker:jsInvoker + devMenuConfigurationDecorator:nil]; +} + +- (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy + bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator + delegate:(id)delegate + jsInvoker:(std::shared_ptr)jsInvoker + devMenuConfigurationDecorator:(RCTDevMenuConfigurationDecorator *)devMenuConfigurationDecorator +{ + return [self initWithBridge:nil + bridgeProxy:bridgeProxy + bridgeModuleDecorator:bridgeModuleDecorator + delegate:delegate + jsInvoker:jsInvoker + devMenuConfigurationDecorator:devMenuConfigurationDecorator]; } /** @@ -770,6 +790,12 @@ - (BOOL)_shouldCreateObjCModule:(Class)moduleClass [(id)module initialize]; } +#if RCT_DEV_MENU + + [_devMenuConfigurationDecorator decorate:module]; + +#endif + /** * Attach method queue to id object. * This is necessary because the id object can be eagerly created/initialized before the method 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 c72cf7cdd6f641..0aa5845a80f3c1 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 @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @class RCTFabricSurface; @class RCTHost; @class RCTModuleRegistry; +@class RCTDevMenuConfiguration; @protocol RCTTurboModuleManagerDelegate; @@ -63,7 +64,15 @@ typedef std::shared_ptr (^RCTHostJSEngineProv hostDelegate:(id)hostDelegate turboModuleManagerDelegate:(id)turboModuleManagerDelegate jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider - launchOptions:(nullable NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER; + launchOptions:(nullable NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration + NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider + hostDelegate:(id)hostDelegate + turboModuleManagerDelegate:(id)turboModuleManagerDelegate + jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider + launchOptions:(nullable NSDictionary *)launchOptions; - (instancetype)initWithBundleURL:(NSURL *)bundleURL hostDelegate:(id)hostDelegate 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 12631915e88d84..5c7bf6b986a7bd 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 @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -123,6 +124,7 @@ @implementation RCTHost { std::vector<__weak RCTFabricSurface *> _attachedSurfaces; RCTModuleRegistry *_moduleRegistry; + RCTDevMenuConfiguration *_devMenuConfiguration; std::unique_ptr _inspectorHostDelegate; std::shared_ptr _inspectorTarget; @@ -159,6 +161,21 @@ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider turboModuleManagerDelegate:(id)turboModuleManagerDelegate jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider launchOptions:(nullable NSDictionary *)launchOptions +{ + return [self initWithBundleURLProvider:provider + hostDelegate:hostDelegate + turboModuleManagerDelegate:turboModuleManagerDelegate + jsEngineProvider:jsEngineProvider + launchOptions:launchOptions + devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; +} + +- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider + hostDelegate:(id)hostDelegate + turboModuleManagerDelegate:(id)turboModuleManagerDelegate + jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider + launchOptions:(nullable NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { if (self = [super init]) { _hostDelegate = hostDelegate; @@ -201,6 +218,7 @@ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider }); _inspectorHostDelegate = std::make_unique(self); + _devMenuConfiguration = devMenuConfiguration; } return self; } @@ -246,7 +264,8 @@ - (void)start turboModuleManagerDelegate:_turboModuleManagerDelegate moduleRegistry:_moduleRegistry parentInspectorTarget:_inspectorTarget.get() - launchOptions:_launchOptions]; + launchOptions:_launchOptions + devMenuConfiguration:_devMenuConfiguration]; [_hostDelegate hostDidStart:self]; } @@ -449,7 +468,8 @@ - (void)_reloadWithShouldRestartSurfaces:(BOOL)shouldRestartSurfaces turboModuleManagerDelegate:_turboModuleManagerDelegate moduleRegistry:_moduleRegistry parentInspectorTarget:_inspectorTarget.get() - launchOptions:_launchOptions]; + launchOptions:_launchOptions + devMenuConfiguration:_devMenuConfiguration]; [_hostDelegate hostDidStart:self]; for (RCTFabricSurface *surface in [self _getAttachedSurfaces]) { 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 5d4ea87be9e9fb..72b7585184e697 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 @@ -32,6 +32,7 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); @class RCTPerformanceLogger; @class RCTSource; @class RCTSurfacePresenter; +@class RCTDevMenuConfiguration; @protocol RCTTurboModuleManagerDelegate; @@ -73,6 +74,15 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget launchOptions:(nullable NSDictionary *)launchOptions; +- (instancetype)initWithDelegate:(id)delegate + jsRuntimeFactory:(std::shared_ptr)jsRuntimeFactory + bundleManager:(RCTBundleManager *)bundleManager + turboModuleManagerDelegate:(id)turboModuleManagerDelegate + moduleRegistry:(RCTModuleRegistry *)moduleRegistry + parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget + launchOptions:(nullable NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration; + - (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args; - (void)callFunctionOnBufferedRuntimeExecutor:(std::function &&)executor; 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 f5e53529e42045..d065b1b7943d0a 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 @@ -22,6 +22,8 @@ #import #import #import +#import +#import #import #import #import @@ -119,6 +121,7 @@ @implementation RCTInstance { // APIs supporting interop with native modules and view managers RCTBridgeModuleDecorator *_bridgeModuleDecorator; + RCTDevMenuConfigurationDecorator *_devMenuConfigurationDecorator; jsinspector_modern::HostTarget *_parentInspectorTarget; } @@ -132,6 +135,25 @@ - (instancetype)initWithDelegate:(id)delegate moduleRegistry:(RCTModuleRegistry *)moduleRegistry parentInspectorTarget:(jsinspector_modern::HostTarget *)parentInspectorTarget launchOptions:(nullable NSDictionary *)launchOptions +{ + return [self initWithDelegate:delegate + jsRuntimeFactory:jsRuntimeFactory + bundleManager:bundleManager + turboModuleManagerDelegate:tmmDelegate + moduleRegistry:moduleRegistry + parentInspectorTarget:parentInspectorTarget + launchOptions:launchOptions + devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; +} + +- (instancetype)initWithDelegate:(id)delegate + jsRuntimeFactory:(std::shared_ptr)jsRuntimeFactory + bundleManager:(RCTBundleManager *)bundleManager + turboModuleManagerDelegate:(id)tmmDelegate + moduleRegistry:(RCTModuleRegistry *)moduleRegistry + parentInspectorTarget:(jsinspector_modern::HostTarget *)parentInspectorTarget + launchOptions:(nullable NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { if (self = [super init]) { _performanceLogger = [RCTPerformanceLogger new]; @@ -146,6 +168,13 @@ - (instancetype)initWithDelegate:(id)delegate moduleRegistry:moduleRegistry bundleManager:bundleManager callableJSModules:[RCTCallableJSModules new]]; + _devMenuConfigurationDecorator = +#if RCT_DEV_MENU + [[RCTDevMenuConfigurationDecorator alloc] initWithDevMenuConfiguration:devMenuConfiguration]; +#else + nil; +#endif + _parentInspectorTarget = parentInspectorTarget; { __weak __typeof(self) weakSelf = self; @@ -326,7 +355,8 @@ - (void)_start _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridgeProxy:bridgeProxy bridgeModuleDecorator:_bridgeModuleDecorator delegate:self - jsInvoker:jsCallInvoker]; + jsInvoker:jsCallInvoker + devMenuConfigurationDecorator:_devMenuConfigurationDecorator]; #if RCT_DEV /** diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm b/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm index 3b9947000e58db..52566073359fc1 100644 --- a/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm +++ b/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm @@ -24,7 +24,7 @@ - (instancetype)init [ShimRCTInstance class], @selector(initWithDelegate: jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:moduleRegistry:parentInspectorTarget - :launchOptions:)); + :launchOptions:devMenuConfiguration:)); RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate)); RCTSwizzleInstanceSelector( [RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:)); @@ -40,7 +40,7 @@ - (void)reset [ShimRCTInstance class], @selector(initWithDelegate: jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:moduleRegistry:parentInspectorTarget - :launchOptions:)); + :launchOptions:devMenuConfiguration:)); RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate)); RCTSwizzleInstanceSelector( [RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:)); @@ -55,6 +55,7 @@ - (instancetype)initWithDelegate:(id)delegate moduleRegistry:(RCTModuleRegistry *)moduleRegistry parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget launchOptions:(NSDictionary *)launchOptions + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { weakShim.initCount++; return self; diff --git a/packages/rn-tester/RNTester/AppDelegate.mm b/packages/rn-tester/RNTester/AppDelegate.mm index cf02164f8945b8..7217807c12205a 100644 --- a/packages/rn-tester/RNTester/AppDelegate.mm +++ b/packages/rn-tester/RNTester/AppDelegate.mm @@ -29,6 +29,10 @@ #define USE_OSS_CODEGEN 0 #endif +#if RCT_DEV_MENU +#import +#endif + static NSString *kBundlePath = @"js/RNTesterApp.ios"; @interface AppDelegate () @@ -43,6 +47,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( self.dependencyProvider = [RCTAppDependencyProvider new]; #endif +#if RCT_DEV_MENU + + RCTDevMenuConfiguration *devMenuConfiguration = [[RCTDevMenuConfiguration alloc] initWithDevMenuEnabled:true + shakeGestureEnabled:true + keyboardShortcutsEnabled:true]; + [self.reactNativeFactory setDevMenuConfiguration:devMenuConfiguration]; + +#endif + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" diff --git a/private/helloworld/ios/HelloWorld/AppDelegate.swift b/private/helloworld/ios/HelloWorld/AppDelegate.swift index 0c86f2f469fe70..78e9a761c08b8e 100644 --- a/private/helloworld/ios/HelloWorld/AppDelegate.swift +++ b/private/helloworld/ios/HelloWorld/AppDelegate.swift @@ -28,6 +28,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory + #if DEBUG + let devMenuConfiguration = RCTDevMenuConfiguration( + devMenuEnabled: true, + shakeGestureEnabled: true, + keyboardShortcutsEnabled: true + ) + reactNativeFactory?.devMenuConfiguration = devMenuConfiguration + #endif + window = UIWindow(frame: UIScreen.main.bounds) factory.startReactNative(