From 29cdee7adedaf407a3b684d23883d35acd9c8d3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:59:05 +0000 Subject: [PATCH 1/7] Initial plan From d288e5d6a553c0e5836291db27f684232000995f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:08:39 +0000 Subject: [PATCH 2/7] Add dev menu support for Fabric (RCTSurfaceHostingView) Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .../RCTSurfaceHostingView.mm | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index 44d8390e2243f6..06048d77580612 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -13,6 +13,10 @@ #import "RCTSurfaceView.h" #import "RCTUtils.h" +#if __has_include() && RCT_DEV +#import +#endif + @interface RCTSurfaceHostingView () @property (nonatomic, assign) BOOL isActivityIndicatorViewVisible; @@ -278,4 +282,25 @@ - (void)surface:(__unused RCTSurface *)surface didChangeIntrinsicSize:(__unused }); } +#if TARGET_OS_OSX // [macOS +- (NSMenu *)menuForEvent:(NSEvent *)event +{ + NSMenu *menu = nil; +#if __has_include() && RCT_DEV + // In Fabric/bridgeless mode, we trigger the dev menu via notification + // This is bridge-independent and works in both bridge and bridgeless modes + [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + + // Note: The DevMenu will display itself via the notification handler + // We return nil here as the menu is shown programmatically + return nil; +#endif + + if (menu == nil) { + menu = [super menuForEvent:event]; + } + return menu; +} +#endif // macOS] + @end From 85e1cfc67c2a1411679713b5287217148b171d38 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:13:28 +0000 Subject: [PATCH 3/7] Add DevMenu to contextContainer and improve menu access in Fabric Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .../RCTSurfaceHostingView.mm | 23 ++++++++++++++----- .../RCTSurfacePresenterBridgeAdapter.mm | 13 +++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index 06048d77580612..469f25b084e36c 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -287,13 +287,24 @@ - (NSMenu *)menuForEvent:(NSEvent *)event { NSMenu *menu = nil; #if __has_include() && RCT_DEV - // In Fabric/bridgeless mode, we trigger the dev menu via notification - // This is bridge-independent and works in both bridge and bridgeless modes - [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + // Try to get the dev menu from the bridge if available + // The contextContainer stores the bridge, but we access it through the surface for simplicity + if ([self.surface respondsToSelector:@selector(bridge)]) { + id bridge = [self.surface performSelector:@selector(bridge)]; + if (bridge && [bridge respondsToSelector:@selector(devMenu)]) { + id devMenu = [bridge performSelector:@selector(devMenu)]; + if (devMenu && [devMenu respondsToSelector:@selector(menu)]) { + menu = [devMenu performSelector:@selector(menu)]; + } + } + } - // Note: The DevMenu will display itself via the notification handler - // We return nil here as the menu is shown programmatically - return nil; + // Fall back to notification-based approach for bridgeless mode or if bridge access failed + if (menu == nil) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + // Return nil as the menu will be shown programmatically via notification + return nil; + } #endif if (menu == nil) { diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm index d3dcaf5bc5dab0..41618f435e119c 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm @@ -18,6 +18,10 @@ #import #import +#if __has_include() && RCT_DEV +#import +#endif + #import #import #import @@ -39,6 +43,15 @@ - (void)invokeAsync:(std::function &&)func; contextContainer->insert("Bridge", wrapManagedObjectWeakly(bridge)); contextContainer->insert("RCTImageLoader", wrapManagedObject((id)imageLoader)); + +#if __has_include() && RCT_DEV + // Add DevMenu to contextContainer for Fabric views to access + RCTDevMenu *devMenu = [bridge devMenu]; + if (devMenu) { + contextContainer->insert("RCTDevMenu", wrapManagedObject(devMenu)); + } +#endif + return contextContainer; } From ca10b385ba4038653149faab98dba3bac82b55f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:16:00 +0000 Subject: [PATCH 4/7] Improve code clarity and comments based on review feedback Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .../SurfaceHostingView/RCTSurfaceHostingView.mm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index 469f25b084e36c..10c14e192e458e 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -288,13 +288,14 @@ - (NSMenu *)menuForEvent:(NSEvent *)event NSMenu *menu = nil; #if __has_include() && RCT_DEV // Try to get the dev menu from the bridge if available - // The contextContainer stores the bridge, but we access it through the surface for simplicity - if ([self.surface respondsToSelector:@selector(bridge)]) { - id bridge = [self.surface performSelector:@selector(bridge)]; - if (bridge && [bridge respondsToSelector:@selector(devMenu)]) { - id devMenu = [bridge performSelector:@selector(devMenu)]; - if (devMenu && [devMenu respondsToSelector:@selector(menu)]) { - menu = [devMenu performSelector:@selector(menu)]; + // In Fabric with bridge mode, RCTSurface (but not RCTFabricSurface) has a bridge + if ([self.surface isKindOfClass:[RCTSurface class]]) { + RCTSurface *surface = (RCTSurface *)self.surface; + // Use performSelector to access the bridge since it's not a public property + if ([surface respondsToSelector:@selector(bridge)]) { + RCTBridge *bridge = [surface performSelector:@selector(bridge)]; + if (bridge) { + menu = [[bridge devMenu] menu]; } } } From 28fb42556fab907ab1679f3c47b0a4095934d59c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:28:27 +0000 Subject: [PATCH 5/7] Remove bridge fallback and use notification-only approach Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .../RCTSurfaceHostingView.mm | 24 ++++--------------- .../RCTSurfacePresenterBridgeAdapter.mm | 13 ---------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index 10c14e192e458e..b1030ddb15d3ac 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -287,25 +287,11 @@ - (NSMenu *)menuForEvent:(NSEvent *)event { NSMenu *menu = nil; #if __has_include() && RCT_DEV - // Try to get the dev menu from the bridge if available - // In Fabric with bridge mode, RCTSurface (but not RCTFabricSurface) has a bridge - if ([self.surface isKindOfClass:[RCTSurface class]]) { - RCTSurface *surface = (RCTSurface *)self.surface; - // Use performSelector to access the bridge since it's not a public property - if ([surface respondsToSelector:@selector(bridge)]) { - RCTBridge *bridge = [surface performSelector:@selector(bridge)]; - if (bridge) { - menu = [[bridge devMenu] menu]; - } - } - } - - // Fall back to notification-based approach for bridgeless mode or if bridge access failed - if (menu == nil) { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; - // Return nil as the menu will be shown programmatically via notification - return nil; - } + // In Fabric/bridgeless mode, trigger the dev menu via notification + // This is bridge-independent and works without accessing the bridge + [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + // Return nil as the menu will be shown programmatically via notification + return nil; #endif if (menu == nil) { diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm index 41618f435e119c..d3dcaf5bc5dab0 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm @@ -18,10 +18,6 @@ #import #import -#if __has_include() && RCT_DEV -#import -#endif - #import #import #import @@ -43,15 +39,6 @@ - (void)invokeAsync:(std::function &&)func; contextContainer->insert("Bridge", wrapManagedObjectWeakly(bridge)); contextContainer->insert("RCTImageLoader", wrapManagedObject((id)imageLoader)); - -#if __has_include() && RCT_DEV - // Add DevMenu to contextContainer for Fabric views to access - RCTDevMenu *devMenu = [bridge devMenu]; - if (devMenu) { - contextContainer->insert("RCTDevMenu", wrapManagedObject(devMenu)); - } -#endif - return contextContainer; } From ee90ce26ba58d094405a19d836559ed79b03c541 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:32:09 +0000 Subject: [PATCH 6/7] Restore DevMenu in contextContainer (bridge adapter only) Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .../Fabric/RCTSurfacePresenterBridgeAdapter.mm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm index d3dcaf5bc5dab0..41618f435e119c 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenterBridgeAdapter.mm @@ -18,6 +18,10 @@ #import #import +#if __has_include() && RCT_DEV +#import +#endif + #import #import #import @@ -39,6 +43,15 @@ - (void)invokeAsync:(std::function &&)func; contextContainer->insert("Bridge", wrapManagedObjectWeakly(bridge)); contextContainer->insert("RCTImageLoader", wrapManagedObject((id)imageLoader)); + +#if __has_include() && RCT_DEV + // Add DevMenu to contextContainer for Fabric views to access + RCTDevMenu *devMenu = [bridge devMenu]; + if (devMenu) { + contextContainer->insert("RCTDevMenu", wrapManagedObject(devMenu)); + } +#endif + return contextContainer; } From 65064330fc2f5fa2b888e5f91e01cf15df4c4438 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:24:41 +0000 Subject: [PATCH 7/7] Access DevMenu from contextContainer instead of using notifications Co-authored-by: Saadnajmi <6722175+Saadnajmi@users.noreply.github.com> --- .../RCTSurfaceHostingView.mm | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index b1030ddb15d3ac..1d94f1b411debd 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -15,6 +15,9 @@ #if __has_include() && RCT_DEV #import +#import +#import +#import #endif @interface RCTSurfaceHostingView () @@ -287,11 +290,30 @@ - (NSMenu *)menuForEvent:(NSEvent *)event { NSMenu *menu = nil; #if __has_include() && RCT_DEV - // In Fabric/bridgeless mode, trigger the dev menu via notification - // This is bridge-independent and works without accessing the bridge - [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; - // Return nil as the menu will be shown programmatically via notification - return nil; + // Try to get DevMenu from the contextContainer via the surface presenter + // This works in Fabric architecture where DevMenu is stored in contextContainer + if ([self.surface respondsToSelector:@selector(surfacePresenter)]) { + RCTSurfacePresenter *surfacePresenter = [self.surface performSelector:@selector(surfacePresenter)]; + if (surfacePresenter) { + auto contextContainer = surfacePresenter.contextContainer; + if (contextContainer) { + auto optionalDevMenu = contextContainer->find>("RCTDevMenu"); + if (optionalDevMenu) { + RCTDevMenu *devMenu = facebook::react::unwrapManagedObject(optionalDevMenu.value()); + if (devMenu) { + menu = [devMenu menu]; + } + } + } + } + } + + // Fall back to notification-based approach if contextContainer access fails + if (menu == nil) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + // Return nil as the menu will be shown programmatically via notification + return nil; + } #endif if (menu == nil) {