From d55f38b04bc63e5b9111c4976e444831b78203c4 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 22 Oct 2026 12:35:24 -0700 Subject: [PATCH 1/3] Read launchOptions from injection in RCTLinkingManager (#59259) Summary: Synthesize the injected launchOptions property and read getInitialURL state from it instead of self.bridge. Drops the last bridge read in this module along with its direct bridge header import. Changelog: [Internal] Reviewed By: shwanton Differential Revision: D126567470 --- .../Libraries/LinkingIOS/RCTLinkingManager.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm b/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm index f25ad54a5f4a..76d6f526d9fa 100644 --- a/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm +++ b/packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm @@ -8,7 +8,6 @@ #import #import -#import #import #import #import @@ -36,6 +35,8 @@ @implementation RCTLinkingManager RCT_EXPORT_MODULE() +@synthesize launchOptions = _launchOptions; + + (void)postNotificationWithURL:(NSURL *)URL { NSDictionary *payload = @{@"url" : URL.absoluteString}; @@ -207,11 +208,10 @@ - (void)canOpenURL:(NSString *)urlString - (void)getInitialURL:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject { NSURL *initialURL = nil; - if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey] != nullptr) { - initialURL = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; + if (self.launchOptions[UIApplicationLaunchOptionsURLKey] != nullptr) { + initialURL = self.launchOptions[UIApplicationLaunchOptionsURLKey]; } else { - NSDictionary *userActivityDictionary = - self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]; + NSDictionary *userActivityDictionary = self.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]; if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) { initialURL = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL; } From 0f80fcc9fe636f9b203cc77a77626d4084371b2e Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 22 Oct 2026 12:35:24 -0700 Subject: [PATCH 2/3] Drop RCTBridge from RCTPerfMonitor (#59258) Summary: Route JS-thread dispatch through the injected dispatchToJSThread block and read the injected performanceLogger. Delete the Views counter, which can only show zero since RCTViewRegistry exposes no enumeration, and reflow the HUD bar. Changelog: [iOS][Removed] - Remove the non-functional Views counter from the PerfMonitor HUD Reviewed By: shwanton Differential Revision: D126567926 --- .../React/CoreModules/RCTPerfMonitor.mm | 66 +++++++------------ 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/packages/react-native/React/CoreModules/RCTPerfMonitor.mm b/packages/react-native/React/CoreModules/RCTPerfMonitor.mm index c33af88055a0..2bb27c1eaebf 100644 --- a/packages/react-native/React/CoreModules/RCTPerfMonitor.mm +++ b/packages/react-native/React/CoreModules/RCTPerfMonitor.mm @@ -17,8 +17,8 @@ #import -#import -#import +#import +#import #import #import #import @@ -58,8 +58,13 @@ static vm_size_t RCTGetResidentMemorySize(void) return memoryUsageInByte; } -@interface RCTPerfMonitor - : NSObject +@interface RCTPerfMonitor : NSObject < + RCTBridgeModule, + RCTTurboModule, + RCTInvalidating, + RCTJSDispatcherModule, + UITableViewDataSource, + UITableViewDelegate> #if __has_include() @property (nonatomic, strong, readonly) RCTDevMenuItem *devMenuItem; @@ -68,7 +73,6 @@ @interface RCTPerfMonitor @property (nonatomic, strong, readonly) UIView *container; @property (nonatomic, strong, readonly) UILabel *memory; @property (nonatomic, strong, readonly) UILabel *heap; -@property (nonatomic, strong, readonly) UILabel *views; @property (nonatomic, strong, readonly) UITableView *metrics; @property (nonatomic, strong, readonly) RCTFPSGraph *jsGraph; @property (nonatomic, strong, readonly) RCTFPSGraph *uiGraph; @@ -85,7 +89,6 @@ @implementation RCTPerfMonitor { UIView *_container; UILabel *_memory; UILabel *_heap; - UILabel *_views; UILabel *_uiGraphLabel; UILabel *_jsGraphLabel; UITableView *_metrics; @@ -109,8 +112,9 @@ @implementation RCTPerfMonitor { NSArray *_perfLoggerMarks; } -@synthesize bridge = _bridge; +@synthesize dispatchToJSThread = _dispatchToJSThread; @synthesize moduleRegistry = _moduleRegistry; +@synthesize performanceLogger = _performanceLogger; RCT_EXPORT_MODULE() @@ -172,7 +176,7 @@ - (UIView *)container UIEdgeInsets safeInsets = RCTKeyWindow().safeAreaInsets; _container = - [[UIView alloc] initWithFrame:CGRectMake(safeInsets.left, safeInsets.top, 180, RCTPerfMonitorBarHeight)]; + [[UIView alloc] initWithFrame:CGRectMake(safeInsets.left, safeInsets.top, 136, RCTPerfMonitorBarHeight)]; _container.layer.borderWidth = 2; _container.layer.borderColor = [UIColor lightGrayColor].CGColor; [_container addGestureRecognizer:self.gestureRecognizer]; @@ -212,22 +216,10 @@ - (UILabel *)heap return _heap; } -- (UILabel *)views -{ - if (_views == nullptr) { - _views = [[UILabel alloc] initWithFrame:CGRectMake(88, 0, 44, RCTPerfMonitorBarHeight)]; - _views.font = [UIFont systemFontOfSize:12]; - _views.numberOfLines = 3; - _views.textAlignment = NSTextAlignmentCenter; - } - - return _views; -} - - (RCTFPSGraph *)uiGraph { if (_uiGraph == nullptr) { - _uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(134, 14, 40, 30) color:[UIColor lightGrayColor]]; + _uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(90, 14, 40, 30) color:[UIColor lightGrayColor]]; } return _uiGraph; } @@ -235,7 +227,7 @@ - (RCTFPSGraph *)uiGraph - (RCTFPSGraph *)jsGraph { if (_jsGraph == nullptr) { - _jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(178, 14, 40, 30) color:[UIColor lightGrayColor]]; + _jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(134, 14, 40, 30) color:[UIColor lightGrayColor]]; } return _jsGraph; } @@ -243,7 +235,7 @@ - (RCTFPSGraph *)jsGraph - (UILabel *)uiGraphLabel { if (_uiGraphLabel == nullptr) { - _uiGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(134, 3, 40, 10)]; + _uiGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(90, 3, 40, 10)]; _uiGraphLabel.font = [UIFont systemFontOfSize:11]; _uiGraphLabel.textAlignment = NSTextAlignmentCenter; _uiGraphLabel.text = @"UI"; @@ -255,7 +247,7 @@ - (UILabel *)uiGraphLabel - (UILabel *)jsGraphLabel { if (_jsGraphLabel == nullptr) { - _jsGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(178, 3, 38, 10)]; + _jsGraphLabel = [[UILabel alloc] initWithFrame:CGRectMake(134, 3, 38, 10)]; _jsGraphLabel.font = [UIFont systemFontOfSize:11]; _jsGraphLabel.textAlignment = NSTextAlignmentCenter; _jsGraphLabel.text = @"JS"; @@ -289,7 +281,6 @@ - (void)show [self.container addSubview:self.memory]; [self.container addSubview:self.heap]; - [self.container addSubview:self.views]; [self.container addSubview:self.uiGraph]; [self.container addSubview:self.uiGraphLabel]; @@ -309,12 +300,12 @@ - (void)show [self.container addSubview:self.jsGraph]; [self.container addSubview:self.jsGraphLabel]; - [_bridge - dispatchBlock:^{ - self->_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(threadUpdate:)]; - [self->_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - } - queue:RCTJSThread]; + if (_dispatchToJSThread != nullptr) { + _dispatchToJSThread(^{ + self->_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(threadUpdate:)]; + [self->_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + }); + } } - (void)hide @@ -415,15 +406,6 @@ - (void)parse:(NSString *)log - (void)updateStats { - NSDictionary *views = [_bridge.uiManager valueForKey:@"viewRegistry"]; - NSUInteger viewCount = views.count; - NSUInteger visibleViewCount = 0; - for (UIView *view in views.allValues) { - if ((view.window != nullptr) || (view.superview.window != nullptr)) { - visibleViewCount++; - } - } - // Ensure the container always stays on top of newly added views if ([_container.superview.subviews lastObject] != _container) { [_container.superview bringSubviewToFront:_container]; @@ -432,8 +414,6 @@ - (void)updateStats double mem = (double)RCTGetResidentMemorySize() / 1024 / 1024; self.memory.text = [NSString stringWithFormat:@"RAM\n%.2lf\nMB", mem]; self.heap.text = [NSString stringWithFormat:@"JSC\n%.2lf\nMB", (double)_heapSize / 1024]; - self.views.text = - [NSString stringWithFormat:@"Views\n%lu\n%lu", (unsigned long)visibleViewCount, (unsigned long)viewCount]; __weak __typeof__(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -481,7 +461,7 @@ - (void)loadPerformanceLoggerData { NSUInteger i = 0; NSMutableArray *data = [NSMutableArray new]; - RCTPerformanceLogger *performanceLogger = [_bridge performanceLogger]; + RCTPerformanceLogger *performanceLogger = _performanceLogger; NSArray *values = [performanceLogger valuesForTags]; for (NSString *label in LabelsForRCTPerformanceLoggerTags()) { long long value = values[i + 1].longLongValue - values[i].longLongValue; From 4a001ae4bc9cd76492b2690fcc76b6760d81d700 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 22 Oct 2026 12:35:24 -0700 Subject: [PATCH 3/3] Remove bridge-mode branches from RCTDevSettings (#59260) Summary: Delete the always-false _isBridgeMode helper and fold its call sites to the bridgeless paths. Remove the no-op profiling bridge calls with their synchronize cascade, and simplify setExecutorClass to persist locally plus reload. Changelog: [Internal] Reviewed By: shwanton Differential Revision: D126574662 --- .../React/CoreModules/RCTDevSettings.mm | 92 ++----------------- 1 file changed, 9 insertions(+), 83 deletions(-) diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.mm b/packages/react-native/React/CoreModules/RCTDevSettings.mm index a204f5620669..abe2897dec27 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.mm +++ b/packages/react-native/React/CoreModules/RCTDevSettings.mm @@ -17,7 +17,6 @@ #import #import #import -#import #import #import #import @@ -165,11 +164,6 @@ + (BOOL)requiresMainQueueSetup return NO; } -- (BOOL)_isBridgeMode -{ - return [self.bridge isKindOfClass:[RCTBridge class]]; -} - - (instancetype)initWithDataSource:(id)dataSource { if (self = [super init]) { @@ -221,36 +215,9 @@ - (void)initialize #endif #if RCT_ENABLE_INSPECTOR - if ([self _isBridgeMode]) { - // We need this dispatch to the main thread because the bridge is not yet - // finished with its initialisation. By the time it relinquishes control of - // the main thread, this operation can be performed. - __weak __typeof(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - id dispatchBlock = ^{ - __typeof(self) strongSelf2 = weakSelf; - if (!strongSelf2) { - return; - } - NSURL *url = strongSelf2.bundleManager.bundleURL; - [RCTInspectorDevServerHelper connectWithBundleURL:url]; - }; - [strongSelf.bridge dispatchBlock:dispatchBlock queue:RCTJSThread]; - }); - } else { - NSURL *url = self.bundleManager.bundleURL; - [RCTInspectorDevServerHelper connectWithBundleURL:url]; - } + NSURL *url = self.bundleManager.bundleURL; + [RCTInspectorDevServerHelper connectWithBundleURL:url]; #endif - - __weak __typeof(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf _synchronizeAllSettings]; - }); } - (dispatch_queue_t)methodQueue @@ -295,11 +262,7 @@ - (void)setIsShakeGestureEnabled:(BOOL)isShakeGestureEnabled - (BOOL)isDeviceDebuggingAvailable { #if RCT_ENABLE_INSPECTOR - if ([self _isBridgeMode]) { - return self.bridge.isInspectable; - } else { - return self.isInspectable; - } + return self.isInspectable; #else return false; #endif // RCT_ENABLE_INSPECTOR @@ -341,7 +304,6 @@ - (BOOL)isShakeToShowDevMenuEnabled - (void)setProfilingEnabled:(BOOL)enabled { [self _updateSettingWithValue:@(enabled) forKey:kRCTDevSettingProfilingEnabled]; - [self _profilingSettingDidChange]; } - (BOOL)isProfilingEnabled @@ -349,25 +311,6 @@ - (BOOL)isProfilingEnabled return [[self settingForKey:kRCTDevSettingProfilingEnabled] boolValue]; } -- (void)_profilingSettingDidChange -{ - BOOL enabled = self.isProfilingEnabled; - if (self.isHotLoadingAvailable && enabled != RCTProfileIsProfiling()) { - if (enabled) { - [self.bridge startProfiling]; - } else { - __weak __typeof(self) weakSelf = self; - [self.bridge stopProfiling:^(NSData *logData) { - __typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - RCTProfileSendResult(strongSelf.bridge, @"systrace", logData); - }]; - } - } -} - - (void)setHotLoadingEnabled:(BOOL)enabled { if (self.isHotLoadingEnabled != enabled) { @@ -430,17 +373,12 @@ - (BOOL)isPerfMonitorShown - (void)setExecutorClass:(Class)executorClass { - _executorClass = executorClass; - if (self.bridge.executorClass != executorClass) { - // TODO (6929129): we can remove this special case test once we have better - // support for custom executors in the dev menu. But right now this is - // needed to prevent overriding a custom executor with the default if a - // custom executor has been set directly on the bridge - if (executorClass == Nil) { - return; - } - - self.bridge.executorClass = executorClass; + if (executorClass == Nil) { + _executorClass = Nil; + return; + } + if (_executorClass != executorClass) { + _executorClass = executorClass; RCTTriggerReloadCommandListeners(@"Custom executor class reset"); } } @@ -505,15 +443,6 @@ - (void)openDebugger #pragma mark - Internal -/** - * Query the data source for all possible settings and make sure we're doing the right - * thing for the state of each setting. - */ -- (void)_synchronizeAllSettings -{ - [self _profilingSettingDidChange]; -} - - (void)jsLoaded:(NSNotification *)notification { // In bridge mode, the bridge that sent the notif must be the same as the one stored in this module. @@ -530,9 +459,6 @@ - (void)jsLoaded:(NSNotification *)notification if (!strongSelf) { return; } - // update state again after the bridge has finished loading - [strongSelf _synchronizeAllSettings]; - // Inspector can only be shown after JS has loaded if ([strongSelf isElementInspectorShown]) { #pragma clang diagnostic push