From 67a4a0f83e7f61e20dd2d5c2e2ad0df3837edc1d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Thu, 1 Aug 2024 17:37:26 -0700 Subject: [PATCH] Refactor Redbox, Logbox, and DevLoadingView as Sheets (#2151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(iOS): adjust RCTRedBox to work for iPad and support orientation changes (#41217) Summary: When opening `RCTRedBox` on an iPad (and also visionOS) there was an issue with buttons width going out of screen. When changing screen orientation, RedBox wasn't recalculating view positions. **Root cause**: Getting frame of root view to display this modal and basing all calculations on it. **Solution**: Use Auto Layout to build UI that responds to orientation changes and device specific modal presentation. I've also tested it with adding custom buttons to RedBox and it works properly. [IOS] [FIXED] - adjust RCTRedBox to work for iPad and support orientation changes Pull Request resolved: https://github.com/facebook/react-native/pull/41217 Test Plan: Launch the app without metro running and check out RedBox that's shown there. Also change screen orientation to see proper recalculation of view positions. https://github.com/facebook/react-native/assets/52801365/892dcfe7-246f-4f36-be37-12c139c207ac https://github.com/facebook/react-native/assets/52801365/dfd0c3d8-5997-462d-97ec-dcc3de452e26 Reviewed By: GijsWeterings Differential Revision: D50734569 Pulled By: javache fbshipit-source-id: 51b854a47caf90ae46fcd32c4adcc64ec2ceb63f * refactor: use less verbose API for RCTRedBox constraints (#42261) Summary: This PR is a continuation of my previous PR where I refactored RCTRedBox to use Auto Layout (https://github.com/facebook/react-native/issues/41217). This PR uses less verbose API for defining constraints. ## Changelog: [IOS] [CHANGED] - use less verbose Auto Layout API for RCTRedBox constraints Pull Request resolved: https://github.com/facebook/react-native/pull/42261 Test Plan: Launch the app without metro enabled to see the RCTRedBox ![CleanShot 2024-01-12 at 14 54 20@2x](https://github.com/facebook/react-native/assets/52801365/32ee9916-3e32-46c3-9f6b-c313631aa1e5) ![CleanShot 2024-01-12 at 14 54 16@2x](https://github.com/facebook/react-native/assets/52801365/c625b9b9-b462-4e67-831f-0192427bbe93) Reviewed By: NickGerleman Differential Revision: D52730458 Pulled By: javache fbshipit-source-id: dc7227e7b6e3238c195342cb0460850b57eb75c3 * refactor Redbox on macOS and present as a sheet * present RCTDevLoadingView as a sheet * refactor Logbox and present as a sheet * change RCTRootView.loadingView to RCTPlatformView * update Podfile.lock * PR feedback --------- Co-authored-by: Oskar Kwaśniewski --- .../react-native/React/Base/RCTRootView.h | 2 +- .../react-native/React/Base/RCTRootView.m | 21 +- packages/react-native/React/Base/RCTUIKit.h | 2 + .../react-native/React/Base/macOS/RCTUIKit.m | 1 + .../React/CoreModules/RCTDevLoadingView.mm | 19 +- .../React/CoreModules/RCTLogBox.mm | 12 - .../React/CoreModules/RCTLogBoxView.h | 28 +- .../React/CoreModules/RCTLogBoxView.mm | 136 +-- .../React/CoreModules/RCTRedBox.mm | 950 +++++++++--------- .../RCTRedBoxExtraDataViewController.h | 8 +- .../RCTRedBoxExtraDataViewController.m | 19 +- packages/rn-tester/Podfile.lock | 219 ++-- 12 files changed, 619 insertions(+), 798 deletions(-) diff --git a/packages/react-native/React/Base/RCTRootView.h b/packages/react-native/React/Base/RCTRootView.h index 2f0f05df4d1f3f..b4d6b8a24a675c 100644 --- a/packages/react-native/React/Base/RCTRootView.h +++ b/packages/react-native/React/Base/RCTRootView.h @@ -135,7 +135,7 @@ extern * with a blank screen. By default this is nil, but you can override it with * (for example) a UIActivityIndicatorView or a placeholder image. */ -@property (nonatomic, strong, nullable) RCTUIView *loadingView; // [macOS] +@property (nonatomic, strong, nullable) RCTPlatformView *loadingView; // [macOS] /** * When set, any touches on the RCTRootView that are not matched up to any of the child diff --git a/packages/react-native/React/Base/RCTRootView.m b/packages/react-native/React/Base/RCTRootView.m index 592043f82df2e5..b49a0e32359b4b 100644 --- a/packages/react-native/React/Base/RCTRootView.m +++ b/packages/react-native/React/Base/RCTRootView.m @@ -166,20 +166,15 @@ - (void)layoutSubviews #if !TARGET_OS_OSX // [macOS] _loadingView.center = (CGPoint){CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)}; #else // [macOS - NSRect bounds = self.bounds; - NSSize loadingViewSize = _loadingView.frame.size; - CGFloat scale = self.window.backingScaleFactor; - if (scale == 0.0 && RCTRunningInTestEnvironment()) { - // When running in the test environment the view is not on screen. - // Use a scaleFactor of 1 so that the test results are machine independent. - scale = 1; - } - RCTAssert(scale != 0.0, @"Layout occurs before the view is in a window?"); - - _loadingView.frameOrigin = NSMakePoint( - RCTRoundPixelValue(bounds.origin.x + ((bounds.size.width - loadingViewSize.width) / 2), scale), - RCTRoundPixelValue(bounds.origin.y + ((bounds.size.height - loadingViewSize.height) / 2), scale) + CGFloat centerX = CGRectGetMidX(self.bounds); + CGFloat centerY = CGRectGetMidY(self.bounds); + NSRect newFrame = NSMakeRect( + centerX - _loadingView.frame.size.width / 2.0, + centerY - _loadingView.frame.size.height / 2.0, + _loadingView.frame.size.width, + _loadingView.frame.size.height ); + _loadingView.frame = newFrame; #endif // macOS] } diff --git a/packages/react-native/React/Base/RCTUIKit.h b/packages/react-native/React/Base/RCTUIKit.h index aad3257664bd5d..20c7a21006611f 100644 --- a/packages/react-native/React/Base/RCTUIKit.h +++ b/packages/react-native/React/Base/RCTUIKit.h @@ -526,9 +526,11 @@ NS_ASSUME_NONNULL_END #if !TARGET_OS_OSX typedef UIApplication RCTUIApplication; typedef UIWindow RCTUIWindow; +typedef UIViewController RCTUIViewController; #else typedef NSApplication RCTUIApplication; typedef NSWindow RCTUIWindow; +typedef NSViewController RCTUIViewController; #endif // diff --git a/packages/react-native/React/Base/macOS/RCTUIKit.m b/packages/react-native/React/Base/macOS/RCTUIKit.m index 75f5c83eec3c6c..09d4dee93971af 100644 --- a/packages/react-native/React/Base/macOS/RCTUIKit.m +++ b/packages/react-native/React/Base/macOS/RCTUIKit.m @@ -522,6 +522,7 @@ - (void)drawRect:(CGRect)rect - (void)layout { + [super layout]; if (self.window != nil) { [self layoutSubviews]; } diff --git a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm index 5895b756cea3ce..0ea90573687e4f 100644 --- a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm +++ b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm @@ -123,7 +123,7 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo if (!self->_window && !RCTRunningInTestEnvironment()) { #if !TARGET_OS_OSX // [macOS] - UIWindow *window = RCTKeyWindow(); + UIWindow *window = RCTKeyWindow(); // [macOS] CGFloat windowWidth = window.bounds.size.width; self->_window = [[UIWindow alloc] initWithWindowScene:window.windowScene]; @@ -138,8 +138,7 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo self->_label.font = [UIFont monospacedDigitSystemFontOfSize:12.0 weight:UIFontWeightRegular]; self->_label.textAlignment = NSTextAlignmentCenter; #else // [macOS - NSRect screenFrame = [NSScreen mainScreen].visibleFrame; - self->_window = [[NSPanel alloc] initWithContentRect:NSMakeRect(screenFrame.origin.x + round((screenFrame.size.width - 375) / 2), screenFrame.size.height - 20, 375, 19) + self->_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 375, 20) styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:YES]; @@ -153,6 +152,7 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo label.selectable = NO; label.wantsLayer = YES; label.layer.cornerRadius = label.frame.size.height / 3; + label.layer.cornerCurve = kCACornerCurveContinuous; self->_label = label; [[self->_window contentView] addSubview:label]; #endif // macOS] @@ -169,7 +169,7 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo self->_label.textColor = color; self->_label.backgroundColor = backgroundColor; - [self->_window orderFront:nil]; + [RCTKeyWindow() beginSheet:self->_window completionHandler:nil]; #endif // macOS] }); @@ -195,10 +195,10 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo dispatch_async(dispatch_get_main_queue(), ^{ self->_hiding = YES; +#if !TARGET_OS_OSX // [macOS] const NSTimeInterval MIN_PRESENTED_TIME = 0.6; NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:self->_showDate]; NSTimeInterval delay = MAX(0, MIN_PRESENTED_TIME - presentedTime); -#if !TARGET_OS_OSX // [macOS] CGRect windowFrame = self->_window.frame; [UIView animateWithDuration:0.25 delay:delay @@ -213,14 +213,7 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo self->_hiding = false; }]; #else // [macOS] - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [NSAnimationContext runAnimationGroup:^(__unused NSAnimationContext *context) { - self->_window.animator.alphaValue = 0.0; - } completionHandler:^{ - [self->_window close]; - self->_window = nil; - }]; - }); + [RCTKeyWindow() endSheet:self->_window]; #endif // macOS] }); } diff --git a/packages/react-native/React/CoreModules/RCTLogBox.mm b/packages/react-native/React/CoreModules/RCTLogBox.mm index 8473fa5f0b21b3..041e46e26c9d8a 100644 --- a/packages/react-native/React/CoreModules/RCTLogBox.mm +++ b/packages/react-native/React/CoreModules/RCTLogBox.mm @@ -55,27 +55,15 @@ - (void)setSurfacePresenter:(id)surfacePresenter } if (strongSelf->_bridgelessSurfacePresenter) { -#if !TARGET_OS_OSX // [macOS] strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() surfacePresenter:strongSelf->_bridgelessSurfacePresenter]; -#else // [macOS - strongSelf->_view = [[RCTLogBoxView alloc] initWithSurfacePresenter:strongSelf->_bridgelessSurfacePresenter]; -#endif // macOS] [strongSelf->_view show]; } else if (strongSelf->_bridge && strongSelf->_bridge.valid) { if (strongSelf->_bridge.surfacePresenter) { -#if !TARGET_OS_OSX // [macOS] strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() surfacePresenter:strongSelf->_bridge.surfacePresenter]; -#else // [macOS - strongSelf->_view = [[RCTLogBoxView alloc] initWithSurfacePresenter:strongSelf->_bridge.surfacePresenter]; -#endif // macOS] } else { -#if !TARGET_OS_OSX // [macOS] strongSelf->_view = [[RCTLogBoxView alloc] initWithWindow:RCTKeyWindow() bridge:strongSelf->_bridge]; -#else // [macOS - strongSelf->_view = [[RCTLogBoxView alloc] initWithBridge:self->_bridge]; -#endif // macOS] } [strongSelf->_view show]; } diff --git a/packages/react-native/React/CoreModules/RCTLogBoxView.h b/packages/react-native/React/CoreModules/RCTLogBoxView.h index 3ec4285cff40c5..39409af37b2479 100644 --- a/packages/react-native/React/CoreModules/RCTLogBoxView.h +++ b/packages/react-native/React/CoreModules/RCTLogBoxView.h @@ -11,31 +11,25 @@ #import // [macOS] #if !TARGET_OS_OSX // [macOS] - @interface RCTLogBoxView : UIWindow +#else // [macOS +@interface RCTLogBoxView : NSWindow +#endif // macOS] +#if !TARGET_OS_OSX // [macOS] - (instancetype)initWithFrame:(CGRect)frame; +#endif // [macOS] -- (void)createRootViewController:(UIView *)view; +- (void)createRootViewController:(RCTUIView *)view; // [macOS] -- (instancetype)initWithWindow:(UIWindow *)window bridge:(RCTBridge *)bridge; -- (instancetype)initWithWindow:(UIWindow *)window surfacePresenter:(id)surfacePresenter; +- (instancetype)initWithWindow:(RCTUIWindow *)window bridge:(RCTBridge *)bridge; // [macOS] +- (instancetype)initWithWindow:(RCTUIWindow *)window surfacePresenter:(id)surfacePresenter; // [macOS] - (void)show; - -@end - -#else // [macOS - -@interface RCTLogBoxView : NSWindow - -- (instancetype)initWithSurfacePresenter:(id)surfacePresenter; -- (instancetype)initWithBridge:(RCTBridge *)bridge; - +#if TARGET_OS_OSX // [macOS - (void)setHidden:(BOOL)hidden; - -- (void)show; +#endif // macOS] @end -#endif // macOS] + diff --git a/packages/react-native/React/CoreModules/RCTLogBoxView.mm b/packages/react-native/React/CoreModules/RCTLogBoxView.mm index f280f44673899e..11656eb45330ba 100644 --- a/packages/react-native/React/CoreModules/RCTLogBoxView.mm +++ b/packages/react-native/React/CoreModules/RCTLogBoxView.mm @@ -11,12 +11,14 @@ #import #import -#if !TARGET_OS_OSX // [macOS] - @implementation RCTLogBoxView { RCTSurface *_surface; +#if TARGET_OS_OSX // [macOS + NSWindow *_window; +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { @@ -25,39 +27,61 @@ - (instancetype)initWithFrame:(CGRect)frame } return self; } +#endif // [macOS] -- (void)createRootViewController:(UIView *)view +- (void)createRootViewController:(RCTUIView *)view // [macOS] { - UIViewController *_rootViewController = [UIViewController new]; + RCTUIViewController *_rootViewController = [RCTUIViewController new]; // [macOS] _rootViewController.view = view; +#if !TARGET_OS_OSX // [macOS] _rootViewController.view.backgroundColor = [UIColor clearColor]; _rootViewController.modalPresentationStyle = UIModalPresentationFullScreen; self.rootViewController = _rootViewController; +#else // [macOS + _rootViewController.view.wantsLayer = true; + _rootViewController.view.layer.backgroundColor = [NSColor clearColor].CGColor; + self.contentViewController = _rootViewController; +#endif // macOS] } -- (instancetype)initWithWindow:(UIWindow *)window bridge:(RCTBridge *)bridge +- (instancetype)initWithWindow:(RCTUIWindow *)window bridge:(RCTBridge *)bridge // [macOS] { RCTErrorNewArchitectureValidation(RCTNotAllowedInFabricWithoutLegacy, @"RCTLogBoxView", nil); +#if !TARGET_OS_OSX // [macOS] self = [super initWithWindowScene:window.windowScene]; self.windowLevel = UIWindowLevelStatusBar - 1; self.backgroundColor = [UIColor clearColor]; +#else // [macOS + NSRect bounds = NSMakeRect(0, 0, 600, 800); + self = [super initWithContentRect:bounds styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:YES]; + + self.level = NSStatusWindowLevel; + self.backgroundColor = [NSColor clearColor]; + + _window = window; +#endif // macOS] _surface = [[RCTSurface alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:@{}]; [_surface start]; - + if (![_surface synchronouslyWaitForStage:RCTSurfaceStageSurfaceDidInitialMounting timeout:1]) { RCTLogInfo(@"Failed to mount LogBox within 1s"); } - [self createRootViewController:(UIView *)_surface.view]; + [self createRootViewController:(RCTUIView *)_surface.view]; // [macOS] return self; } -- (instancetype)initWithWindow:(UIWindow *)window surfacePresenter:(id)surfacePresenter +- (instancetype)initWithWindow:(RCTUIWindow *)window surfacePresenter:(id)surfacePresenter // [macOS] { +#if !TARGET_OS_OSX // [macOS] self = [super initWithWindowScene:window.windowScene]; +#else // [macOS + self = [super initWithContentRect:NSMakeRect(0, 0, 600, 800) styleMask:NSWindowStyleMaskTitled backing:NSBackingStoreBuffered defer:YES]; + _window = window; +#endif // macOS] id surface = [surfacePresenter createFabricSurfaceForModuleName:@"LogBox" initialProperties:@{}]; [surface start]; @@ -69,107 +93,45 @@ - (instancetype)initWithWindow:(UIWindow *)window surfacePresenter:(id)surfacePresenter -{ - NSRect bounds = NSMakeRect(0, 0, 600, 800); - if ((self = [self initWithContentRect:bounds - styleMask:NSWindowStyleMaskTitled - backing:NSBackingStoreBuffered - defer:YES])) { - id surface = [surfacePresenter createFabricSurfaceForModuleName:@"LogBox" - initialProperties:@{}]; - [surface start]; - RCTSurfaceHostingView *rootView = [[RCTSurfaceHostingView alloc] - initWithSurface:surface - sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact]; - - self.contentView = rootView; - self.contentView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - } - return self; -} - -- (instancetype)initWithBridge:(RCTBridge *)bridge -{ - NSRect bounds = NSMakeRect(0, 0, 600, 800); - if ((self = [self initWithContentRect:bounds - styleMask:NSWindowStyleMaskTitled - backing:NSBackingStoreBuffered - defer:YES])) { - _surface = [[RCTSurface alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:@{}]; - - [_surface start]; - [_surface setSize:bounds.size]; - - if (![_surface synchronouslyWaitForStage:RCTSurfaceStageSurfaceDidInitialMounting timeout:1]) { - RCTLogInfo(@"Failed to mount LogBox within 1s"); - } - - self.contentView = (NSView *)_surface.view; - self.contentView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - } - return self; -} - -- (void)setHidden:(BOOL)hidden // [macOS -{ - if (hidden) { - if (NSApp.modalWindow == self) { - [NSApp stopModal]; - } - [self orderOut:nil]; - } -} // macOS] - - (void)show { - if (!RCTRunningInTestEnvironment()) { - // Run the modal loop outside of the dispatch queue because it is not reentrant. - [self performSelectorOnMainThread:@selector(_showModal) withObject:nil waitUntilDone:NO]; - } - else { - [NSApp activateIgnoringOtherApps:YES]; - [self makeKeyAndOrderFront:nil]; - } + [_window beginSheet:self completionHandler:nil]; } -- (void)_showModal +- (void)setHidden:(BOOL)hidden { - NSModalSession session = [NSApp beginModalSessionForWindow:self]; - - while ([NSApp runModalSession:session] == NSModalResponseContinue) { - // Spin the runloop so that the main dispatch queue is processed. - [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; - } - - [NSApp endModalSession:session]; + [_window endSheet:self]; } +#endif // [macOS] @end - -#endif // macOS] diff --git a/packages/react-native/React/CoreModules/RCTRedBox.mm b/packages/react-native/React/CoreModules/RCTRedBox.mm index 736494693ebc21..f0f4561863b3cb 100644 --- a/packages/react-native/React/CoreModules/RCTRedBox.mm +++ b/packages/react-native/React/CoreModules/RCTRedBox.mm @@ -14,9 +14,7 @@ #import #import #import -#if !TARGET_OS_OSX // [macOS] #import -#endif // [macOS] #import #import #import @@ -27,18 +25,29 @@ #if RCT_DEV_MENU -@class RCTRedBoxWindow; +@class RCTRedBoxController; #if !TARGET_OS_OSX // [macOS] @interface UIButton (RCTRedBox) +#else // [macOS +@interface NSButton (RCTRedBox) +#endif // macOS] @property (nonatomic) RCTRedBoxButtonPressHandler rct_handler; +#if !TARGET_OS_OSX // [macOS] - (void)rct_addBlock:(RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents; +#else // [macOS +- (void)rct_addBlock:(RCTRedBoxButtonPressHandler)handler; +#endif // macOS] @end +#if !TARGET_OS_OSX // [macOS] @implementation UIButton (RCTRedBox) +#else // [macOS +@implementation NSButton (RCTRedBox) +#endif // macOS] - (RCTRedBoxButtonPressHandler)rct_handler { @@ -57,132 +66,250 @@ - (void)rct_callBlock } } -- (void)rct_addBlock:(RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents +#if !TARGET_OS_OSX // [macOS] +- (void)rct_addBlock:(RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents; +#else // [macOS +- (void)rct_addBlock:(RCTRedBoxButtonPressHandler)handler +#endif // macOS] { self.rct_handler = handler; +#if !TARGET_OS_OSX // [macOS] [self addTarget:self action:@selector(rct_callBlock) forControlEvents:controlEvents]; +#else // [macOS + [self setTarget:self]; + [self setAction:@selector(rct_callBlock)]; +#endif // macOS] } @end -#endif // [macOS] -@protocol RCTRedBoxWindowActionDelegate +@protocol RCTRedBoxControllerActionDelegate -- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; -- (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow; +- (void)redBoxController:(RCTRedBoxController *)redBoxController openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; +- (void)reloadFromRedBoxController:(RCTRedBoxController *)redBoxController; - (void)loadExtraDataViewController; @end #if !TARGET_OS_OSX // [macOS] -@interface RCTRedBoxWindow : NSObject -@property (nonatomic, strong) UIViewController *rootViewController; -@property (nonatomic, weak) id actionDelegate; +@interface RCTRedBoxController : UIViewController +#else // [macOS +@interface RCTRedBoxController : NSViewController +#endif // macOS] +@property (nonatomic, weak) id actionDelegate; @end -@implementation RCTRedBoxWindow { +@implementation RCTRedBoxController { +#if !TARGET_OS_OSX // [macOS] UITableView *_stackTraceTableView; +#else // [macOS + NSTableView *_stackTraceTableView; +#endif // macOS] NSString *_lastErrorMessage; NSArray *_lastStackTrace; + NSArray *_customButtonTitles; + NSArray *_customButtonHandlers; int _lastErrorCookie; } -- (instancetype)initWithFrame:(CGRect)frame - customButtonTitles:(NSArray *)customButtonTitles - customButtonHandlers:(NSArray *)customButtonHandlers +- (instancetype)initWithCustomButtonTitles:(NSArray *)customButtonTitles + customButtonHandlers:(NSArray *)customButtonHandlers { if (self = [super init]) { _lastErrorCookie = -1; + _customButtonTitles = customButtonTitles; + _customButtonHandlers = customButtonHandlers; + } + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; +#if !TARGET_OS_OSX // [macOS] + self.view.backgroundColor = [UIColor blackColor]; +#else // [macOS + self.view.wantsLayer = YES; + self.view.layer.backgroundColor = [[NSColor blackColor] CGColor]; +#endif // macOS] + + const CGFloat buttonHeight = 60; - _rootViewController = [UIViewController new]; - UIView *rootView = _rootViewController.view; - rootView.frame = frame; - rootView.backgroundColor = [UIColor blackColor]; - - const CGFloat buttonHeight = 60; - - CGRect detailsFrame = rootView.bounds; - detailsFrame.size.height -= buttonHeight + (double)[self bottomSafeViewHeight]; - - _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; - _stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _stackTraceTableView.delegate = self; - _stackTraceTableView.dataSource = self; - _stackTraceTableView.backgroundColor = [UIColor clearColor]; - _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3]; - _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; - _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; - [rootView addSubview:_stackTraceTableView]; - -#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST - NSString *reloadText = @"Reload\n(\u2318R)"; - NSString *dismissText = @"Dismiss\n(ESC)"; - NSString *copyText = @"Copy\n(\u2325\u2318C)"; - NSString *extraText = @"Extra Info\n(\u2318E)"; + CGRect detailsFrame = self.view.bounds; + detailsFrame.size.height -= buttonHeight + (double)[self bottomSafeViewHeight]; + +#if !TARGET_OS_OSX // [macOS] + _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; + _stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _stackTraceTableView.delegate = self; + _stackTraceTableView.dataSource = self; + _stackTraceTableView.backgroundColor = [UIColor clearColor]; + _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3]; + _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; + [self.view addSubview:_stackTraceTableView]; +#else // [macOS + NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; + scrollView.translatesAutoresizingMaskIntoConstraints = NO; + scrollView.autoresizesSubviews = YES; + scrollView.drawsBackground = NO; + + _stackTraceTableView = [[NSTableView alloc] initWithFrame:NSZeroRect]; + _stackTraceTableView.translatesAutoresizingMaskIntoConstraints = NO; + _stackTraceTableView.dataSource = self; + _stackTraceTableView.delegate = self; + _stackTraceTableView.headerView = nil; + _stackTraceTableView.allowsColumnReordering = NO; + _stackTraceTableView.allowsColumnResizing = NO; + _stackTraceTableView.columnAutoresizingStyle = NSTableViewFirstColumnOnlyAutoresizingStyle; + _stackTraceTableView.backgroundColor = [NSColor clearColor]; + _stackTraceTableView.allowsTypeSelect = NO; + + NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:@"info"]; + [_stackTraceTableView addTableColumn:tableColumn]; + + scrollView.documentView = _stackTraceTableView; + [self.view addSubview:scrollView]; +#endif // macOS] + +#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST || TARGET_OS_OSX // [macOS] + NSString *reloadText = @"Reload\n(\u2318R)"; + NSString *dismissText = @"Dismiss\n(ESC)"; + NSString *copyText = @"Copy\n(\u2325\u2318C)"; + NSString *extraText = @"Extra Info\n(\u2318E)"; #else - NSString *reloadText = @"Reload JS"; - NSString *dismissText = @"Dismiss"; - NSString *copyText = @"Copy"; - NSString *extraText = @"Extra Info"; + NSString *reloadText = @"Reload JS"; + NSString *dismissText = @"Dismiss"; + NSString *copyText = @"Copy"; + NSString *extraText = @"Extra Info"; #endif - UIButton *dismissButton = [self redBoxButton:dismissText - accessibilityIdentifier:@"redbox-dismiss" - selector:@selector(dismiss) - block:nil]; - UIButton *reloadButton = [self redBoxButton:reloadText - accessibilityIdentifier:@"redbox-reload" - selector:@selector(reload) - block:nil]; - UIButton *copyButton = [self redBoxButton:copyText - accessibilityIdentifier:@"redbox-copy" - selector:@selector(copyStack) +#if !TARGET_OS_OSX // [macOS] + UIButton *dismissButton = [self redBoxButton:dismissText + accessibilityIdentifier:@"redbox-dismiss" + selector:@selector(dismiss) + block:nil]; + UIButton *reloadButton = [self redBoxButton:reloadText + accessibilityIdentifier:@"redbox-reload" + selector:@selector(reload) block:nil]; - UIButton *extraButton = [self redBoxButton:extraText - accessibilityIdentifier:@"redbox-extra" - selector:@selector(showExtraDataViewController) + UIButton *copyButton = [self redBoxButton:copyText + accessibilityIdentifier:@"redbox-copy" + selector:@selector(copyStack) + block:nil]; + UIButton *extraButton = [self redBoxButton:extraText + accessibilityIdentifier:@"redbox-extra" + selector:@selector(showExtraDataViewController) + block:nil]; +#else // [macOS + NSButton *dismissButton = [self redBoxButton:dismissText + accessibilityIdentifier:@"redbox-dismiss" + selector:@selector(dismiss) block:nil]; + [dismissButton setKeyEquivalent:@"\e"]; + NSButton *reloadButton = [self redBoxButton:reloadText + accessibilityIdentifier:@"redbox-reload" + selector:@selector(reload) + block:nil]; + [reloadButton setKeyEquivalent:@"r"]; + [reloadButton setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + NSButton *copyButton = [self redBoxButton:copyText + accessibilityIdentifier:@"redbox-copy" + selector:@selector(copyStack) + block:nil]; + [copyButton setKeyEquivalent:@"c"]; + [copyButton setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand]; + NSButton *extraButton = [self redBoxButton:extraText + accessibilityIdentifier:@"redbox-extra" + selector:@selector(showExtraDataViewController) + block:nil]; +#endif // macOS] - CGFloat buttonWidth = frame.size.width / (CGFloat)(4 + [customButtonTitles count]); - CGFloat bottomButtonHeight = frame.size.height - buttonHeight - (CGFloat)[self bottomSafeViewHeight]; - dismissButton.frame = CGRectMake(0, bottomButtonHeight, buttonWidth, buttonHeight); - reloadButton.frame = CGRectMake(buttonWidth, bottomButtonHeight, buttonWidth, buttonHeight); - copyButton.frame = CGRectMake(buttonWidth * 2, bottomButtonHeight, buttonWidth, buttonHeight); - extraButton.frame = CGRectMake(buttonWidth * 3, bottomButtonHeight, buttonWidth, buttonHeight); - - [rootView addSubview:dismissButton]; - [rootView addSubview:reloadButton]; - [rootView addSubview:copyButton]; - [rootView addSubview:extraButton]; - - for (NSUInteger i = 0; i < [customButtonTitles count]; i++) { - UIButton *button = [self redBoxButton:customButtonTitles[i] - accessibilityIdentifier:@"" - selector:nil - block:customButtonHandlers[i]]; - button.frame = CGRectMake(buttonWidth * (double)(4 + i), bottomButtonHeight, buttonWidth, buttonHeight); - [rootView addSubview:button]; - } + [NSLayoutConstraint activateConstraints:@[ + [dismissButton.heightAnchor constraintEqualToConstant:buttonHeight], + [reloadButton.heightAnchor constraintEqualToConstant:buttonHeight], + [copyButton.heightAnchor constraintEqualToConstant:buttonHeight], + [extraButton.heightAnchor constraintEqualToConstant:buttonHeight] + ]]; + +#if !TARGET_OS_OSX // [macOS] + UIStackView *buttonStackView = [[UIStackView alloc] init]; + buttonStackView.translatesAutoresizingMaskIntoConstraints = NO; + buttonStackView.axis = UILayoutConstraintAxisHorizontal; + buttonStackView.distribution = UIStackViewDistributionFillEqually; + buttonStackView.alignment = UIStackViewAlignmentTop; + buttonStackView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1]; +#else // [macOS + NSStackView *buttonStackView = [[NSStackView alloc] init]; + buttonStackView.translatesAutoresizingMaskIntoConstraints = NO; + buttonStackView.orientation = NSUserInterfaceLayoutOrientationHorizontal; + buttonStackView.distribution = NSStackViewDistributionFillEqually; + buttonStackView.alignment = NSLayoutAttributeTop; + buttonStackView.wantsLayer = YES; + buttonStackView.layer.backgroundColor = [NSColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1].CGColor; +#endif // macOS] + + [buttonStackView addArrangedSubview:dismissButton]; + [buttonStackView addArrangedSubview:reloadButton]; + [buttonStackView addArrangedSubview:copyButton]; + [buttonStackView addArrangedSubview:extraButton]; - UIView *topBorder = - [[UIView alloc] initWithFrame:CGRectMake(0, bottomButtonHeight + 1, rootView.frame.size.width, 1)]; - topBorder.backgroundColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; + [self.view addSubview:buttonStackView]; - [rootView addSubview:topBorder]; + [NSLayoutConstraint activateConstraints:@[ + [buttonStackView.heightAnchor constraintEqualToConstant:buttonHeight + [self bottomSafeViewHeight]], + [buttonStackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [buttonStackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [buttonStackView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor] + ]]; - UIView *bottomSafeView = [UIView new]; - bottomSafeView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1]; - bottomSafeView.frame = CGRectMake( - 0, - frame.size.height - (CGFloat)[self bottomSafeViewHeight], - frame.size.width, - (CGFloat)[self bottomSafeViewHeight]); - [rootView addSubview:bottomSafeView]; + for (NSUInteger i = 0; i < [_customButtonTitles count]; i++) { +#if !TARGET_OS_OSX // [macOS] + UIButton *button = [self redBoxButton:_customButtonTitles[i] + accessibilityIdentifier:@"" + selector:nil + block:_customButtonHandlers[i]]; +#else // [macOS + NSButton *button = [self redBoxButton:_customButtonTitles[i] + accessibilityIdentifier:@"" + selector:nil + block:_customButtonHandlers[i]]; +#endif // macOS] + [button.heightAnchor constraintEqualToConstant:buttonHeight].active = YES; + [buttonStackView addArrangedSubview:button]; } - return self; + + RCTPlatformView *topBorder = [[RCTPlatformView alloc] init]; // [macOS] + topBorder.translatesAutoresizingMaskIntoConstraints = NO; +#if !TARGET_OS_OSX // [macOS] + topBorder.backgroundColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; +#else // [macOS + topBorder.wantsLayer = true; + topBorder.layer.backgroundColor = [NSColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0].CGColor; +#endif // macOS] + [topBorder.heightAnchor constraintEqualToConstant:1].active = YES; + + [self.view addSubview:topBorder]; + + [NSLayoutConstraint activateConstraints:@[ + [topBorder.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [topBorder.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], + [topBorder.bottomAnchor constraintEqualToAnchor:buttonStackView.topAnchor] + ]]; + +#if TARGET_OS_OSX // [macOS + [NSLayoutConstraint activateConstraints:@[ + [[scrollView leadingAnchor] constraintEqualToAnchor:[[self view] leadingAnchor]], + [[scrollView topAnchor] constraintEqualToAnchor:[[self view] topAnchor]], + [[scrollView trailingAnchor] constraintEqualToAnchor:[[self view] trailingAnchor]], + [[scrollView bottomAnchor] constraintEqualToAnchor:[topBorder topAnchor]], + ]]; +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] - (UIButton *)redBoxButton:(NSString *)title accessibilityIdentifier:(NSString *)accessibilityIdentifier selector:(SEL)selector @@ -206,10 +333,42 @@ - (UIButton *)redBoxButton:(NSString *)title } return button; } +#else // [macOS +- (NSButton *)redBoxButton:(NSString *)title + accessibilityIdentifier:(NSString *)accessibilityIdentifier + selector:(SEL)selector + block:(RCTRedBoxButtonPressHandler)block +{ + NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect]; + button.translatesAutoresizingMaskIntoConstraints = NO; + button.accessibilityIdentifier = @"accessibilityIdentifier"; + button.bordered = NO; + NSAttributedString *attributedTitle = [[NSAttributedString alloc] + initWithString:title + attributes:@{NSForegroundColorAttributeName : [ NSColor whiteColor] }]; + button.attributedTitle = attributedTitle; + [button setButtonType:NSButtonTypeMomentaryPushIn]; + if (selector) { + button.target = self; + button.action = selector; + } else if (block) { + [button rct_addBlock:block]; + } + return button; +} +#endif // macOS] - (NSInteger)bottomSafeViewHeight { +#if !TARGET_OS_OSX // [macOS] return RCTSharedApplication().delegate.window.safeAreaInsets.bottom; +#else // [macOS + if (@available(macOS 12.0, *)) { + return RCTSharedApplication().keyWindow.screen.safeAreaInsets.bottom; + } else { + return 0; + } +#endif // macOS] } RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) @@ -231,7 +390,7 @@ - (void)showErrorMessage:(NSString *)message // Remove ANSI color codes from the message NSString *messageWithoutAnsi = [self stripAnsi:message]; - BOOL isRootViewControllerPresented = self.rootViewController.presentingViewController != nil; + BOOL isRootViewControllerPresented = self.presentingViewController != nil; // Show if this is a new message, or if we're updating the previous message BOOL isNew = !isRootViewControllerPresented && !isUpdate; BOOL isUpdateForSameMessage = !isNew && @@ -248,22 +407,31 @@ - (void)showErrorMessage:(NSString *)message [_stackTraceTableView reloadData]; if (!isRootViewControllerPresented) { +#if !TARGET_OS_OSX // [macOS] [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; - [RCTKeyWindow().rootViewController presentViewController:self.rootViewController animated:YES completion:nil]; + [RCTKeyWindow().rootViewController presentViewController:self animated:YES completion:nil]; +#else // [macOS + [_stackTraceTableView scrollRowToVisible:0]; + [[RCTKeyWindow() contentViewController] presentViewControllerAsSheet:self]; +#endif // macOS] } } } - (void)dismiss { - [self.rootViewController dismissViewControllerAnimated:YES completion:nil]; +#if !TARGET_OS_OSX // [macOS] + [self dismissViewControllerAnimated:YES completion:nil]; +#else // [macOS + [[RCTKeyWindow() contentViewController] dismissViewController:self]; +#endif // macOS] } - (void)reload { - [_actionDelegate reloadFromRedBoxWindow:self]; + [_actionDelegate reloadFromRedBoxController:self]; } - (void)showExtraDataViewController @@ -274,22 +442,28 @@ - (void)showExtraDataViewController - (void)copyStack { NSMutableString *fullStackTrace; - + if (_lastErrorMessage != nil) { fullStackTrace = [_lastErrorMessage mutableCopy]; [fullStackTrace appendString:@"\n\n"]; } else { fullStackTrace = [NSMutableString string]; } - + for (RCTJSStackFrame *stackFrame in _lastStackTrace) { [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]]; if (stackFrame.file) { [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]]; } } +#if !TARGET_OS_OSX // [macOS] UIPasteboard *pb = [UIPasteboard generalPasteboard]; [pb setString:fullStackTrace]; +#else // [macOS + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard setString:fullStackTrace forType:NSPasteboardTypeString]; +#endif // macOS] } - (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame @@ -305,6 +479,7 @@ - (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame #pragma mark - TableView +#if !TARGET_OS_OSX // [macOS] - (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView { return 2; @@ -314,7 +489,14 @@ - (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:( { return section == 0 ? 1 : _lastStackTrace.count; } +#else // [macOS +- (NSInteger)numberOfRowsInTableView:(__unused NSTableView *)tableView +{ + return (_lastErrorMessage != nil) + _lastStackTrace.count; +} +#endif // macOS] +#if !TARGET_OS_OSX // [macOS] - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { @@ -326,7 +508,22 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N RCTJSStackFrame *stackFrame = _lastStackTrace[index]; return [self reuseCell:cell forStackFrame:stackFrame]; } +#else // [macOS +- (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row +{ + if (row == 0) { + NSTableCellView *cell = [tableView makeViewWithIdentifier:@"msg-cell" owner:nil]; + return [self reuseCell:cell forErrorMessage:_lastErrorMessage]; + } + NSTableCellView *cell = [tableView makeViewWithIdentifier:@"cell" owner:nil]; + NSUInteger index = row - 1; + RCTJSStackFrame *stackFrame = _lastStackTrace[index]; + return [self reuseCell:cell forStackFrame:stackFrame]; +} +#endif // macOS] + +#if !TARGET_OS_OSX - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message { if (!cell) { @@ -349,7 +546,56 @@ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString return cell; } +#else // [macOS +- (NSTableCellView *)reuseCell:(NSTableCellView *)cell forErrorMessage:(NSString *)message +{ + if (!cell) { + cell = [[NSTableCellView alloc] initWithFrame:NSZeroRect]; + cell.rowSizeStyle = NSTableViewRowSizeStyleCustom; + cell.textField.accessibilityIdentifier = @"red box-error"; + NSTextField *label = [[NSTextField alloc] initWithFrame:NSZeroRect]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.drawsBackground = NO; + label.bezeled = NO; + label.editable = NO; + + [cell addSubview:label]; + cell.textField = label; + + [NSLayoutConstraint activateConstraints:@[ + [[label leadingAnchor] constraintEqualToAnchor:[cell leadingAnchor] constant:5], + [[label topAnchor] constraintEqualToAnchor:[cell topAnchor] constant:5], + [[label trailingAnchor] constraintEqualToAnchor:[cell trailingAnchor] constant:-5], + [[label bottomAnchor] constraintEqualToAnchor:[cell bottomAnchor] constant:-5], + ]]; + + // Prefer a monofont for formatting messages that were designed + // to be displayed in a terminal. + cell.textField.font = [NSFont monospacedSystemFontOfSize:14 weight:NSFontWeightBold]; + + cell.textField.lineBreakMode = NSLineBreakByWordWrapping; + cell.textField.maximumNumberOfLines = 0; + cell.wantsLayer = true; + cell.layer.cornerRadius = 8.0; + cell.layer.cornerCurve = kCACornerCurveContinuous; + + cell.layer.backgroundColor = [NSColor colorWithRed:0.82 green:0.10 blue:0.15 alpha:1.0].CGColor; + } + + NSDictionary *attributes = @{ + NSForegroundColorAttributeName : [NSColor whiteColor], + NSFontAttributeName : [NSFont systemFontOfSize:16], + }; + NSAttributedString *title = [[NSAttributedString alloc] initWithString:message attributes:attributes]; + + cell.textField.attributedStringValue = title; + + return cell; +} +#endif // [macOS] + +#if !TARGET_OS_OSX // [macOS] - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStackFrame *)stackFrame { if (!cell) { @@ -376,15 +622,96 @@ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStack : [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; return cell; } +#else // [macOS +- (NSTableCellView *)reuseCell:(NSTableCellView *)cell forStackFrame:(RCTJSStackFrame *)stackFrame +{ + if (!cell) { + cell = [[NSTableCellView alloc] initWithFrame:NSZeroRect]; + + NSTextField *label = [[NSTextField alloc] initWithFrame:NSZeroRect]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.backgroundColor = [NSColor clearColor]; + label.bezeled = NO; + label.editable = NO; + + label.maximumNumberOfLines = 2; + + [cell addSubview:label]; + cell.textField = label; + + [NSLayoutConstraint activateConstraints:@[ + [[label leadingAnchor] constraintEqualToAnchor:[cell leadingAnchor] constant:5], + [[label topAnchor] constraintEqualToAnchor:[cell topAnchor]], + [[label trailingAnchor] constraintEqualToAnchor:[cell trailingAnchor] constant:-5], + [[label bottomAnchor] constraintEqualToAnchor:[cell bottomAnchor]], + ]]; + } + + NSString *text = stackFrame.methodName ?: @"(unnamed method)"; + + NSMutableParagraphStyle *textParagraphStyle = [NSMutableParagraphStyle new]; + textParagraphStyle.lineBreakMode = NSLineBreakByCharWrapping; + + NSDictionary *textAttributes = @{ + NSForegroundColorAttributeName : stackFrame.collapse ? [NSColor lightGrayColor] : [NSColor whiteColor], + NSFontAttributeName : [NSFont fontWithName:@"Menlo-Regular" size:14], + NSParagraphStyleAttributeName : textParagraphStyle, + }; + + NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:textAttributes]; + + + NSMutableAttributedString *title = [attributedText mutableCopy]; + + // NSTableCellView doesn't contain a subtitle text field. Rather than define our own custom row view, + // let's append the detail text with a new line if it is needed. + if (stackFrame.file) { + cell.textField.maximumNumberOfLines = 3; + + NSString *detailText = [self formatFrameSource:stackFrame]; + + NSMutableParagraphStyle *detailTextParagraphStyle = [NSMutableParagraphStyle new]; + detailTextParagraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle; + + NSDictionary *detailTextAttributes = @{ + NSForegroundColorAttributeName : stackFrame.collapse ? + [NSColor colorWithRed:0.50 green:0.50 blue:0.50 alpha:1.0] : + [NSColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0], + NSFontAttributeName : [NSFont fontWithName:@"Menlo-Regular" size:11], + NSParagraphStyleAttributeName : detailTextParagraphStyle, + }; + NSAttributedString *attributedDetailText = [[NSAttributedString alloc] initWithString:detailText attributes:detailTextAttributes]; + + [title appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + [title appendAttributedString:attributedDetailText]; + } + + cell.textField.attributedStringValue = title; + + return cell; +} +#endif // macOS] +#if !TARGET_OS_OSX // [macOS] - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +#else // [macOS +- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row +#endif // macOS] { +#if !TARGET_OS_OSX // [macOS] if (indexPath.section == 0) { +#else // [macOS + if (row == 0) { +#endif // macOS] NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; NSDictionary *attributes = +#if !TARGET_OS_OSX // [macOS] @{NSFontAttributeName : [UIFont boldSystemFontOfSize:16], NSParagraphStyleAttributeName : paragraphStyle}; +#else // [macOS + @{NSFontAttributeName : [NSFont boldSystemFontOfSize:16], NSParagraphStyleAttributeName : paragraphStyle}; +#endif // macOS] CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin @@ -396,18 +723,31 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa } } +#if !TARGET_OS_OSX // [macOS - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 1) { NSUInteger row = indexPath.row; RCTJSStackFrame *stackFrame = _lastStackTrace[row]; - [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame]; + [_actionDelegate redBoxController:self openStackFrameInEditor:stackFrame]; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } +#else // [macOS +- (BOOL)tableView:(__unused NSTableView *)tableView shouldSelectRow:(__unused NSInteger)row +{ + if (row != 0) { + NSUInteger index = row - 1; + RCTJSStackFrame *stackFrame = _lastStackTrace[index]; + [_actionDelegate redBoxController:self openStackFrameInEditor:stackFrame]; + } + return NO; +} +#endif // macOS] #pragma mark - Key commands +#if !TARGET_OS_OSX // [macOS] - (NSArray *)keyCommands { // NOTE: We could use RCTKeyCommands for this, but since @@ -438,371 +778,21 @@ - (BOOL)canBecomeFirstResponder { return YES; } +#endif // [macOS] @end -#else // [macOS - -@interface RCTRedBoxScrollView : NSScrollView -@end - -@implementation RCTRedBoxScrollView - -- (NSSize)intrinsicContentSize -{ - NSView *documentView = self.documentView; - return documentView != nil ? documentView.intrinsicContentSize : super.intrinsicContentSize; -} - -@end - -@interface RCTRedBoxWindow : NSObject - -- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack isUpdate:(BOOL)isUpdate; -- (void)dismiss; - -@property (nonatomic, weak) id actionDelegate; - -@end - -@implementation RCTRedBoxWindow -{ - NSWindow *_window; - NSTableView *_stackTraceTableView; - NSString *_lastErrorMessage; - NSArray *_lastStackTrace; - BOOL _visible; -} - -- (instancetype)init -{ - if ((self = [super init])) { - _window = [[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:NSWindowStyleMaskTitled backing:NSBackingStoreBuffered defer:YES]; - _window.backgroundColor = [NSColor colorWithRed:0.8 green:0 blue:0 alpha:1]; - _window.animationBehavior = NSWindowAnimationBehaviorDocumentWindow; - - NSScrollView *scrollView = [[RCTRedBoxScrollView alloc] initWithFrame:NSZeroRect]; - scrollView.translatesAutoresizingMaskIntoConstraints = NO; - scrollView.backgroundColor = [NSColor clearColor]; - scrollView.drawsBackground = NO; - scrollView.hasVerticalScroller = YES; - - NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:@"info"]; - tableColumn.editable = false; - tableColumn.resizingMask = NSTableColumnAutoresizingMask; - - _stackTraceTableView = [[NSTableView alloc] initWithFrame:NSZeroRect]; - _stackTraceTableView.dataSource = self; - _stackTraceTableView.delegate = self; - _stackTraceTableView.headerView = nil; - _stackTraceTableView.allowsColumnReordering = NO; - _stackTraceTableView.allowsColumnResizing = NO; - _stackTraceTableView.columnAutoresizingStyle = NSTableViewFirstColumnOnlyAutoresizingStyle; - _stackTraceTableView.backgroundColor = [NSColor clearColor]; - _stackTraceTableView.allowsTypeSelect = NO; - [_stackTraceTableView addTableColumn:tableColumn]; - scrollView.documentView = _stackTraceTableView; - - NSButton *dismissButton = [[NSButton alloc] initWithFrame:NSZeroRect]; - dismissButton.accessibilityIdentifier = @"redbox-dismiss"; - dismissButton.translatesAutoresizingMaskIntoConstraints = NO; - dismissButton.target = self; - dismissButton.action = @selector(dismiss:); - [dismissButton setButtonType:NSButtonTypeMomentaryPushIn]; - dismissButton.bezelStyle = NSBezelStyleRounded; - dismissButton.title = @"Dismiss (Esc)"; - dismissButton.keyEquivalent = @"\e"; - [dismissButton setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; - - NSButton *reloadButton = [[NSButton alloc] initWithFrame:NSZeroRect]; - reloadButton.accessibilityIdentifier = @"redbox-reload"; - reloadButton.translatesAutoresizingMaskIntoConstraints = NO; - reloadButton.target = self; - reloadButton.action = @selector(reload:); - reloadButton.bezelStyle = NSBezelStyleRounded; - reloadButton.title = @"Reload JS (\u2318R)"; - [reloadButton setButtonType:NSButtonTypeMomentaryPushIn]; - reloadButton.keyEquivalent = @"r"; - reloadButton.keyEquivalentModifierMask = NSEventModifierFlagCommand; - [reloadButton setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; - [reloadButton setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; - - NSButton *copyButton = [[NSButton alloc] initWithFrame:NSZeroRect]; - copyButton.accessibilityIdentifier = @"redbox-copy"; - copyButton.translatesAutoresizingMaskIntoConstraints = NO; - copyButton.target = self; - copyButton.action = @selector(copyStack:); - copyButton.title = @"Copy (\u2325\u2318C)"; - copyButton.bezelStyle = NSBezelStyleRounded; - [copyButton setButtonType:NSButtonTypeMomentaryPushIn]; - copyButton.keyEquivalent = @"c"; - copyButton.keyEquivalentModifierMask = NSEventModifierFlagOption | NSEventModifierFlagCommand; - [copyButton setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; - - NSView *contentView = _window.contentView; - [contentView addSubview:scrollView]; - [contentView addSubview:dismissButton]; - [contentView addSubview:reloadButton]; - [contentView addSubview:copyButton]; - - [NSLayoutConstraint activateConstraints:@[ - // the window shouldn't be any bigger than 375x643 points - [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:375], - [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:643], - // scroll view hugs the left, top, and right sides of the window, and the buttons at the bottom - [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:16], - [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1 constant:16], - [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:-16], - [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:reloadButton attribute:NSLayoutAttributeTop multiplier:1 constant:-8], - // buttons have equal widths - [NSLayoutConstraint constraintWithItem:dismissButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:reloadButton attribute:NSLayoutAttributeWidth multiplier:1 constant:0], - [NSLayoutConstraint constraintWithItem:dismissButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:copyButton attribute:NSLayoutAttributeWidth multiplier:1 constant:0], - // buttons are centered horizontally in the window - [NSLayoutConstraint constraintWithItem:dismissButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:contentView attribute:NSLayoutAttributeLeading multiplier:1 constant:16], - [NSLayoutConstraint constraintWithItem:copyButton attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationLessThanOrEqual toItem:contentView attribute:NSLayoutAttributeTrailing multiplier:1 constant:-16], - [NSLayoutConstraint constraintWithItem:dismissButton attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:reloadButton attribute:NSLayoutAttributeLeading multiplier:1 constant:-8], - [NSLayoutConstraint constraintWithItem:reloadButton attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0], - [NSLayoutConstraint constraintWithItem:copyButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:reloadButton attribute:NSLayoutAttributeTrailing multiplier:1 constant:8], - // buttons are baseline aligned - [NSLayoutConstraint constraintWithItem:dismissButton attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:reloadButton attribute:NSLayoutAttributeBaseline multiplier:1 constant:0], - [NSLayoutConstraint constraintWithItem:dismissButton attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:copyButton attribute:NSLayoutAttributeBaseline multiplier:1 constant:0], - // buttons appear at the bottom of the window - [NSLayoutConstraint constraintWithItem:reloadButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1 constant:-16], - ]]; - } - return self; -} - -- (void)dealloc -{ - // VSO#1878643: On macOS the RedBox can be dealloc'd on the JS thread causing the Main Thread Checker to throw when the NSTableView properties below are accessed. - NSTableView *stackTraceTableView = _stackTraceTableView; - RCTUnsafeExecuteOnMainQueueSync(^{ - stackTraceTableView.dataSource = nil; - stackTraceTableView.delegate = nil; - }); -} - -- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack isUpdate:(BOOL)isUpdate errorCookie:(int)errorCookie -{ - // Show if this is a new message, or if we're updating the previous message - if ((!_visible && !isUpdate) || (_visible && isUpdate && [_lastErrorMessage isEqualToString:message])) { - _lastStackTrace = stack; - - // message is displayed using UILabel, which is unable to render text of - // unlimited length, so we truncate it - _lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)]; - - [_window layoutIfNeeded]; // layout the window for the correct width - [_stackTraceTableView reloadData]; // load the new data - [_stackTraceTableView.enclosingScrollView invalidateIntrinsicContentSize]; // the height of the scroll view changed with the new data - [_window layoutIfNeeded]; // layout the window for the correct height - - if (!_visible) { - _visible = YES; - [_window center]; - if (!RCTRunningInTestEnvironment()) { - // Run the modal loop outside of the dispatch queue because it is not reentrant. - [self performSelectorOnMainThread:@selector(showModal) withObject:nil waitUntilDone:NO]; - } - else { - [NSApp activateIgnoringOtherApps:YES]; - [_window makeKeyAndOrderFront:nil]; - } - } - } -} - -- (void)showModal -{ - NSModalSession session = [NSApp beginModalSessionForWindow:_window]; - - while ([NSApp runModalSession:session] == NSModalResponseContinue) { - // Spin the runloop so that the main dispatch queue is processed. - [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; - } - - [NSApp endModalSession:session]; -} - -- (void)dismiss -{ - if (_visible) { - [NSApp stopModal]; - [_window orderOut:self]; - _visible = NO; - } -} - -- (IBAction)dismiss:(__unused NSButton *)sender -{ - [self dismiss]; -} - -- (IBAction)reload:(__unused NSButton *)sender -{ - [_actionDelegate reloadFromRedBoxWindow:self]; -} - -- (IBAction)copyStack:(__unused NSButton *)sender -{ - // TODO: This is copy/paste from the iOS implementation - NSMutableString *fullStackTrace; - - if (_lastErrorMessage != nil) { - fullStackTrace = [_lastErrorMessage mutableCopy]; - [fullStackTrace appendString:@"\n\n"]; - } - else { - fullStackTrace = [NSMutableString string]; - } - - for (RCTJSStackFrame *stackFrame in _lastStackTrace) { - [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]]; - if (stackFrame.file) { - [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]]; - } - } - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard clearContents]; - [pasteboard setString:fullStackTrace forType:NSPasteboardTypeString]; -} - -- (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame -{ - // TODO: This is copy/paste from the iOS implementation - NSString *lineInfo = [NSString stringWithFormat:@"%@:%zd", - [stackFrame.file lastPathComponent], - stackFrame.lineNumber]; - - if (stackFrame.column != 0) { - lineInfo = [lineInfo stringByAppendingFormat:@":%zd", stackFrame.column]; - } - return lineInfo; -} - -#pragma mark - TableView - -- (NSInteger)numberOfRowsInTableView:(__unused NSTableView *)tableView -{ - return (_lastErrorMessage != nil) + _lastStackTrace.count; -} - -- (BOOL)tableView:(__unused NSTableView *)tableView shouldSelectRow:(__unused NSInteger)row -{ - return NO; -} - -- (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row -{ - NSTableCellView *view = [tableView makeViewWithIdentifier:tableColumn.identifier owner:nil]; - - if (view == nil) { - view = [[NSTableCellView alloc] initWithFrame:NSZeroRect]; - view.identifier = tableColumn.identifier; - - NSTextField *label = [[NSTextField alloc] initWithFrame:NSZeroRect]; - label.translatesAutoresizingMaskIntoConstraints = NO; - label.backgroundColor = [NSColor clearColor]; - label.drawsBackground = NO; - label.bezeled = NO; - label.editable = NO; - [label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; - [label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; - - [view addSubview:label]; - view.textField = label; - - [NSLayoutConstraint activateConstraints:@[ - [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeLeading multiplier:1 constant:0], - [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTop multiplier:1 constant:0], - [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0], - [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeBottom multiplier:1 constant:0], - ]]; - } - - view.textField.attributedStringValue = [self attributedStringForRow:row]; - - return view; -} - -- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row -{ - NSAttributedString *attributedString = [self attributedStringForRow:row]; - NSRect boundingRect = [attributedString boundingRectWithSize:NSMakeSize(tableView.frame.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin]; - CGFloat height = ceilf(NSMaxY(boundingRect)); - - if (row == 0) { - height += 32; - } - - return height; -} - -- (NSAttributedString *)attributedStringForRow:(NSUInteger)row -{ - if (_lastErrorMessage != nil) { - if (row == 0) { - NSDictionary *attributes = @{ - NSForegroundColorAttributeName : [NSColor whiteColor], - NSFontAttributeName : [NSFont systemFontOfSize:16], - }; - return [[NSAttributedString alloc] initWithString:_lastErrorMessage attributes:attributes]; - } - --row; - } - - RCTJSStackFrame *stackFrame = _lastStackTrace[row]; - - NSMutableParagraphStyle *titleParagraphStyle = [NSMutableParagraphStyle new]; - titleParagraphStyle.lineBreakMode = NSLineBreakByCharWrapping; - - NSDictionary *titleAttributes = @{ - NSForegroundColorAttributeName : [NSColor colorWithWhite:1 alpha:0.9], - NSFontAttributeName : [NSFont fontWithName:@"Menlo-Regular" size:14], - NSParagraphStyleAttributeName : titleParagraphStyle, - }; - - NSString *rawTitle = stackFrame.methodName ?: @"(unnamed method)"; - NSAttributedString *title = [[NSAttributedString alloc] initWithString:rawTitle attributes:titleAttributes]; - if (stackFrame.file == nil) { - return title; - } - - NSMutableParagraphStyle *frameParagraphStyle = [NSMutableParagraphStyle new]; - frameParagraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle; - - NSDictionary *frameAttributes = @{ - NSForegroundColorAttributeName : [NSColor colorWithWhite:1 alpha:0.7], - NSFontAttributeName : [NSFont fontWithName:@"Menlo-Regular" size:11], - NSParagraphStyleAttributeName : frameParagraphStyle, - }; - - NSMutableAttributedString *frameSource = [[NSMutableAttributedString alloc] initWithString:[self formatFrameSource:stackFrame] attributes:frameAttributes]; - [frameSource replaceCharactersInRange:NSMakeRange(0, 0) withString:@"\n"]; - [frameSource insertAttributedString:title atIndex:0]; - return frameSource; -} - -@end - -#endif // macOS] @interface RCTRedBox () < RCTInvalidating, - RCTRedBoxWindowActionDelegate, -#if !TARGET_OS_OSX // [macOS] + RCTRedBoxControllerActionDelegate, RCTRedBoxExtraDataActionDelegate, -#endif // [macOS] NativeRedBoxSpec> @end @implementation RCTRedBox { - RCTRedBoxWindow *_window; + RCTRedBoxController *_controller; NSMutableArray> *_errorCustomizers; -#if !TARGET_OS_OSX // [macOS] RCTRedBoxExtraDataViewController *_extraDataViewController; -#endif // [macOS] NSMutableArray *_customButtonTitles; NSMutableArray *_customButtonHandlers; } @@ -938,74 +928,53 @@ - (void)showErrorMessage:(NSString *)message errorCookie:(int)errorCookie { dispatch_async(dispatch_get_main_queue(), ^{ -#if !TARGET_OS_OSX // [macOS] if (self->_extraDataViewController == nil) { self->_extraDataViewController = [RCTRedBoxExtraDataViewController new]; self->_extraDataViewController.actionDelegate = self; } -#endif // [macOS] #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[self->_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"collectRedBoxExtraData" body:nil]; #pragma clang diagnostic pop - - if (!self->_window) { -#if !TARGET_OS_OSX // [macOS] -#if !TARGET_OS_VISION // [macOS] - self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds - customButtonTitles:self->_customButtonTitles - customButtonHandlers:self->_customButtonHandlers]; -#else // [visionOS - self->_window = [[RCTRedBoxWindow alloc] initWithFrame:CGRectMake(0, 0, 1280, 720) - customButtonTitles:self->_customButtonTitles - customButtonHandlers:self->_customButtonHandlers]; -#endif // visionOS] -#else // [macOS - self->_window = [RCTRedBoxWindow new]; -#endif // macOS] - self->_window.actionDelegate = self; + if (!self->_controller) { + self->_controller = [[RCTRedBoxController alloc] initWithCustomButtonTitles:self->_customButtonTitles + customButtonHandlers:self->_customButtonHandlers]; + self->_controller.actionDelegate = self; } RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message stack:stack]; errorInfo = [self _customizeError:errorInfo]; - [self->_window showErrorMessage:errorInfo.errorMessage - withStack:errorInfo.stack - isUpdate:isUpdate - errorCookie:errorCookie]; + [self->_controller showErrorMessage:errorInfo.errorMessage + withStack:errorInfo.stack + isUpdate:isUpdate + errorCookie:errorCookie]; }); } - (void)loadExtraDataViewController { -#if !TARGET_OS_OSX // [macOS] dispatch_async(dispatch_get_main_queue(), ^{ +#if !TARGET_OS_OSX // [macOS] // Make sure the CMD+E shortcut doesn't call this twice - if (self->_extraDataViewController != nil && ![self->_window.rootViewController presentedViewController]) { - [self->_window.rootViewController presentViewController:self->_extraDataViewController - animated:YES - completion:nil]; + if (self->_extraDataViewController != nil && ![self->_controller presentedViewController]) { + [self->_controller presentViewController:self->_extraDataViewController animated:YES completion:nil]; } - }); +#else // [macOS + // Do nothing, as we haven't implemented `RCTRedBoxExtraDataViewController` on macOS yet #endif // [macOS] + }); } RCT_EXPORT_METHOD(setExtraData:(NSDictionary *)extraData forIdentifier:(NSString *)identifier) { -#if !TARGET_OS_OSX // [macOS] [_extraDataViewController addExtraData:extraData forIdentifier:identifier]; -#endif // [macOS] } RCT_EXPORT_METHOD(dismiss) { -#if TARGET_OS_OSX // [macOS - [self->_window performSelectorOnMainThread:@selector(dismiss) withObject:nil waitUntilDone:NO]; -#else // [macOS dispatch_async(dispatch_get_main_queue(), ^{ - [self->_window dismiss]; - self->_window = nil; // [macOS] release _window now to ensure its UIKit ivars are dealloc'd on the main thread as the RCTRedBox can be dealloc'd on a background thread. + [self->_controller dismiss]; }); -#endif // macOS] } - (void)invalidate @@ -1013,7 +982,8 @@ - (void)invalidate [self dismiss]; } -- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame +- (void)redBoxController:(__unused RCTRedBoxController *)redBoxController + openStackFrameInEditor:(RCTJSStackFrame *)stackFrame { NSURL *const bundleURL = _overrideBundleURL ?: _bundleManager.bundleURL; if (![bundleURL.scheme hasPrefix:@"http"]) { @@ -1036,10 +1006,10 @@ - (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEd - (void)reload { // Window is not used and can be nil - [self reloadFromRedBoxWindow:nil]; + [self reloadFromRedBoxController:nil]; } -- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow +- (void)reloadFromRedBoxController:(__unused RCTRedBoxController *)redBoxController { if (_overrideReloadAction) { _overrideReloadAction(); diff --git a/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.h b/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.h index 7fa65162613921..e81b64c281807e 100644 --- a/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.h +++ b/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.h @@ -5,19 +5,21 @@ * LICENSE file in the root directory of this source tree. */ -#if !TARGET_OS_OSX // [macOS] -#import +#import // [macOS] @protocol RCTRedBoxExtraDataActionDelegate - (void)reload; @end +#if !TARGET_OS_OSX // [macOS] @interface RCTRedBoxExtraDataViewController : UIViewController +#else // [macOS +@interface RCTRedBoxExtraDataViewController : NSViewController +#endif // macOS] @property (nonatomic, weak) id actionDelegate; - (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier; @end -#endif // [macOS] diff --git a/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.m b/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.m index 702bad5a873e03..10ebde9fc73f71 100644 --- a/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.m +++ b/packages/react-native/React/Modules/RCTRedBoxExtraDataViewController.m @@ -5,18 +5,25 @@ * LICENSE file in the root directory of this source tree. */ -#if !TARGET_OS_OSX // [macOS] + #import "RCTRedBoxExtraDataViewController.h" +#if !TARGET_OS_OSX // [macOS] @interface RCTRedBoxExtraDataCell : UITableViewCell +#else // [macOS +@interface RCTRedBoxExtraDataCell : NSTableCellView +#endif // macOS] +#if !TARGET_OS_OSX // [macOS] @property (nonatomic, strong) UILabel *keyLabel; @property (nonatomic, strong) UILabel *valueLabel; +#endif // macOS] @end @implementation RCTRedBoxExtraDataCell +#if !TARGET_OS_OSX // [macOS] - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { @@ -52,6 +59,7 @@ - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSStr } return self; } +#endif // [macOS] @end @@ -60,13 +68,18 @@ @interface RCTRedBoxExtraDataViewController () @end @implementation RCTRedBoxExtraDataViewController { +#if !TARGET_OS_OSX // [macOS] UITableView *_tableView; +#else // [macOS + NSTableView *_tableView; +#endif // macOS] NSMutableArray *_extraDataTitle; NSMutableArray *_extraData; } @synthesize actionDelegate = _actionDelegate; +#if !TARGET_OS_OSX // [macOS] - (instancetype)init { if (self = [super init]) { @@ -199,6 +212,7 @@ - (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView { return _extraDataTitle.count; } +#endif // [macOS] - (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier { @@ -223,6 +237,7 @@ - (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier }); } +#if !TARGET_OS_OSX // [macOS] - (void)dismiss { [self dismissViewControllerAnimated:YES completion:nil]; @@ -244,6 +259,6 @@ - (void)reload [UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:@selector(reload)] ]; } +#endif // [macOS] @end -#endif // [macOS] diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 47ee80fab4a167..4559ada2f62590 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -2,46 +2,15 @@ PODS: - boost (1.83.0) - DoubleConversion (1.1.6) - FBLazyVector (1000.0.0) + - FBReactNativeSpec (1000.0.0): + - RCT-Folly (= 2023.08.07.00) + - RCTRequired (= 1000.0.0) + - RCTTypeSafety (= 1000.0.0) + - React-Core (= 1000.0.0) + - React-jsi (= 1000.0.0) + - ReactCommon/turbomodule/core (= 1000.0.0) - fmt (9.1.0) - glog (0.3.5) - - MyNativeView (0.0.1): - - glog - - RCT-Folly (= 2023.08.07.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - NativeCxxModuleExample (0.0.1): - - glog - - RCT-Folly (= 2023.08.07.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - OCMock (3.9.3) - RCT-Folly (2023.08.07.00): - boost @@ -80,20 +49,17 @@ PODS: - React-callinvoker (1000.0.0) - React-Codegen (1000.0.0): - DoubleConversion + - FBReactNativeSpec - glog - RCT-Folly - RCTRequired - RCTTypeSafety - React-Core - - React-debug - - React-Fabric - - React-graphics - React-jsc - React-jsi - React-jsiexecutor - React-NativeModulesApple - - React-rendererdebug - - React-utils + - React-rncore - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - React-Core (1000.0.0): @@ -866,8 +832,6 @@ PODS: - React-jsi (= 1000.0.0) - React-perflogger (= 1000.0.0) - React-jsinspector (1000.0.0) - - React-jsitracing (1000.0.0): - - React-jsi - React-logger (1000.0.0): - glog - React-Mapbuffer (1000.0.0): @@ -899,20 +863,13 @@ PODS: - RCTTypeSafety - React-Core - React-CoreModules - - React-debug - - React-Fabric - - React-graphics - React-jsc - React-nativeconfig - React-NativeModulesApple - React-RCTFabric - React-RCTImage - React-RCTNetwork - - React-rendererdebug - - React-RuntimeApple - - React-RuntimeCore - React-runtimescheduler - - React-utils - ReactCommon/turbomodule/core - React-RCTBlob (1000.0.0): - RCT-Folly (= 2023.08.07.00) @@ -993,32 +950,6 @@ PODS: - RCT-Folly (= 2023.08.07.00) - React-debug - React-rncore (1000.0.0) - - React-RuntimeApple (1000.0.0): - - RCT-Folly/Fabric (= 2023.08.07.00) - - React-callinvoker - - React-Core/Default - - React-CoreModules - - React-cxxreact - - React-jsc - - React-jserrorhandler - - React-jsi - - React-jsiexecutor - - React-Mapbuffer - - React-NativeModulesApple - - React-RCTFabric - - React-RuntimeCore - - React-runtimeexecutor - - React-utils - - React-RuntimeCore (1000.0.0): - - glog - - RCT-Folly/Fabric (= 2023.08.07.00) - - React-cxxreact - - React-jsc - - React-jserrorhandler - - React-jsi - - React-jsiexecutor - - React-runtimeexecutor - - React-runtimescheduler - React-runtimeexecutor (1000.0.0): - React-jsi (= 1000.0.0) - React-runtimescheduler (1000.0.0): @@ -1067,22 +998,7 @@ PODS: - ScreenshotManager (0.0.1): - glog - RCT-Folly (= 2023.08.07.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - React-Core - - React-debug - - React-Fabric - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - SocketRocket (0.7.0) - Yoga (1.14.0) @@ -1090,10 +1006,9 @@ DEPENDENCIES: - boost (from `../react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../react-native/Libraries/FBLazyVector`) + - FBReactNativeSpec (from `../react-native/React/FBReactNativeSpec`) - fmt (from `../react-native/third-party-podspecs/fmt.podspec`) - glog (from `../react-native/third-party-podspecs/glog.podspec`) - - MyNativeView (from `NativeComponentExample`) - - NativeCxxModuleExample (from `NativeCxxModuleExample`) - OCMock (~> 3.9.1) - RCT-Folly (from `../react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -1112,12 +1027,10 @@ DEPENDENCIES: - React-graphics (from `../react-native/ReactCommon/react/renderer/graphics`) - React-ImageManager (from `../react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - React-jsc (from `../react-native/ReactCommon/jsc`) - - React-jsc/Fabric (from `../react-native/ReactCommon/jsc`) - React-jserrorhandler (from `../react-native/ReactCommon/jserrorhandler`) - React-jsi (from `../react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../react-native/ReactCommon/jsinspector-modern`) - - React-jsitracing (from `../react-native/ReactCommon/hermes/executor/`) - React-logger (from `../react-native/ReactCommon/logger`) - React-Mapbuffer (from `../react-native/ReactCommon`) - React-nativeconfig (from `../react-native/ReactCommon`) @@ -1138,8 +1051,6 @@ DEPENDENCIES: - React-RCTVibration (from `../react-native/Libraries/Vibration`) - React-rendererdebug (from `../react-native/ReactCommon/react/renderer/debug`) - React-rncore (from `../react-native/ReactCommon`) - - React-RuntimeApple (from `../react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../react-native/ReactCommon/react/runtime`) - React-runtimeexecutor (from `../react-native/ReactCommon/runtimeexecutor`) - React-runtimescheduler (from `../react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../react-native/ReactCommon/react/utils`) @@ -1160,14 +1071,12 @@ EXTERNAL SOURCES: :podspec: "../react-native/third-party-podspecs/DoubleConversion.podspec" FBLazyVector: :path: "../react-native/Libraries/FBLazyVector" + FBReactNativeSpec: + :path: "../react-native/React/FBReactNativeSpec" fmt: :podspec: "../react-native/third-party-podspecs/fmt.podspec" glog: :podspec: "../react-native/third-party-podspecs/glog.podspec" - MyNativeView: - :path: NativeComponentExample - NativeCxxModuleExample: - :path: NativeCxxModuleExample RCT-Folly: :podspec: "../react-native/third-party-podspecs/RCT-Folly.podspec" RCTRequired: @@ -1206,8 +1115,6 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/jsiexecutor" React-jsinspector: :path: "../react-native/ReactCommon/jsinspector-modern" - React-jsitracing: - :path: "../react-native/ReactCommon/hermes/executor/" React-logger: :path: "../react-native/ReactCommon/logger" React-Mapbuffer: @@ -1248,10 +1155,6 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/react/renderer/debug" React-rncore: :path: "../react-native/ReactCommon" - React-RuntimeApple: - :path: "../react-native/ReactCommon/react/runtime/platform/ios" - React-RuntimeCore: - :path: "../react-native/ReactCommon/react/runtime" React-runtimeexecutor: :path: "../react-native/ReactCommon/runtimeexecutor" React-runtimescheduler: @@ -1272,62 +1175,58 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 0686b6af8cbd638c784fea5afb789be66699823c DoubleConversion: ca54355f8932558971f6643521d62b9bc8231cee - FBLazyVector: 13594cc2c4da792806868d79173f27ff976613b3 + FBLazyVector: 22cb5f625004fd6b45b03020131d417f852e35bb + FBReactNativeSpec: 79ff6ddde208700586d0e344549bb9cf1c8fd9c7 fmt: 03574da4b7ba40de39da59677ca66610ce8c4a02 glog: 3a72874c0322c7caf24931d3a2777cb7a3090529 - MyNativeView: 6cb0c7d3ae020a85cad002a6d9118750729e4e91 - NativeCxxModuleExample: 7fc41b0e5eafa575e581916df12de1f6407faf20 OCMock: 300b1b1b9155cb6378660b981c2557448830bdc6 RCT-Folly: e0ca497e3ebd44051a537b0e95c1dcfa3f4adb8f - RCTRequired: ec67360771e1a976bac5ed873345a75ba0adcb7b - RCTTypeSafety: 5218f167c52e3f87a7df471f0a30df3a8ba33465 - React: 018827c2813a2e9e3fb0a1527b17353a542054be - React-callinvoker: 2fa244c3b3ce3c502e34de4163b6a8f1c5972602 - React-Codegen: 45962e8bd5c611779190bbaec2b1541837906576 - React-Core: 27b561f622d60ca18f2a14ff688c0b0bbed348b6 - React-CoreModules: 323339a5b67bcfeed4c8f357f5ff27303592b756 - React-cxxreact: a799a601a6f0e36103e36a1ededcf3f03159d3e0 - React-debug: 574a1a5d0c9305f7b8f683b197a96599bc0bba78 - React-Fabric: 006a1b37e8c7ae3b2c89e0772fd7ca1890b35b25 - React-FabricImage: 61f3ecde18fa320fea233448b2178ae075c4f049 - React-graphics: 7f104a00573d3ac1c81e94dd282cb94cf67991bd - React-ImageManager: 6cf1890f8c3ade795dcf2b54b266412e5c33a213 - React-jsc: a02e7898a610296918e333746dbb10a07b03650a - React-jserrorhandler: 7670a9b54ccfb3d1574c5122d20ad474ffa83e7c - React-jsi: ba7875ee0f0acd7af6a9d61ca9fcd16526600de3 - React-jsiexecutor: da2e93427a66e6f5b06086f7a6325558754ac3a8 - React-jsinspector: 1ed30c68a3c2828315744cbce7b071bd8d0313d7 - React-jsitracing: 3d7ebf6fc4d229fef0d9ed2e51fadb9aee34be8f - React-logger: 75e96c0db8be46e810e3cc5a15e0ffdca537dacd - React-Mapbuffer: 2e43a5a9a317f8ca3bfc42d1e3353f54cc4decf1 - React-nativeconfig: 77ad01da345ece765cf6babfa4db37e276de2663 - React-NativeModulesApple: 994e53995a69d3df63ec677b36e9485754565ed2 - React-perflogger: c2469a2a1f9167c06250e6bcff5b7a4e5bf84a5a - React-RCTActionSheet: 531895fa278c80eec242f7fb048eec1ad3c8c642 - React-RCTAnimation: 0accc050caf0c6e2319d4671575acb4dbfbd5ba3 - React-RCTAppDelegate: 0ab1e42f41b1ab0889f21b09a196ea3163bcfa9b - React-RCTBlob: 973d25279348cd23625f28559a733934897b9008 - React-RCTFabric: 57f628ad6f87c51209eec66f74a3ecbb09903ba6 - React-RCTImage: f2a0c56e845196220d1b59c95f55c68c741d036b - React-RCTLinking: cf1af2304382a2e230c44885ac6d586fc1ed115b - React-RCTNetwork: e862b009499c50e4612ecc605c29913827049222 - React-RCTPushNotification: 1e7460e6bec344bc38dbaa76078f98fb93f08bc8 - React-RCTSettings: e536749aba8b9fd8c30408073b28b3246347873d - React-RCTTest: beafcecd4f31d28d04024e154fb7fb8936f173de - React-RCTText: 8c26abc0325c6e1ce8c7c2f83373bcf6d72731e8 - React-RCTVibration: 4b0637d7953ce3db36f53c849567c6838ef1247a - React-rendererdebug: 931314933469ab3f2e12a263d94936c131cdd411 - React-rncore: 2d0652b4614151b6221142950b371437b3162a78 - React-RuntimeApple: fe392c36edd0e1887c525c03d0df36bb004305e6 - React-RuntimeCore: 71fc743cb1d57487a58de67100d84fd09c985743 - React-runtimeexecutor: 2d4e057f9627b687ace73f3c3a0646242ee2cb60 - React-runtimescheduler: 7deed0f16b07c9f82721c69dee1f7389064a2d1b - React-utils: 91eaf0a0feba835a4e3eb8bd368ff9259789cde7 - ReactCommon: a3c60b933163c736735323eac2392cb270db93eb - ReactCommon-Samples: 870897396f5c528cdc2c8fc49da8be7f70876faf - ScreenshotManager: 9940d0179e2ee682548232f9e406b7d23d8b581d + RCTRequired: f41721269a7406208b800d9e9edd034dea71020a + RCTTypeSafety: 7c43ce01976d1f127750213f3012d33acbb2b612 + React: c88009a795b7288d698e1b30a1e46427dfd528fc + React-callinvoker: 6232e46a091073845741d1cf8711e629926062e9 + React-Codegen: 9b7fc89f7163d73816eb03fee0d9b870402bc44d + React-Core: 27b99964288dcaff0b7b3e2f44a2f0e719c2d6a5 + React-CoreModules: b200dbe462dd272300f89bcf39e4d66c1e31645f + React-cxxreact: f054052dd7fe4364b7d68f88b95cc30c919f4fe8 + React-debug: ef4bb1b979f65ee51ee495d68630ec121b7628c2 + React-Fabric: 12baab38bb2e30a746a52c587c6f01371ef45f8d + React-FabricImage: 2c7a668aa756b1959330d27b16123abd594ba5fc + React-graphics: b66916e05a3fb12ad5103f304297fac316854054 + React-ImageManager: 126a4f40a26c88b5fbf98b89b000a87623ff0b9e + React-jsc: 05ae492a6ae8ea1ea1dd2c4faa739a047ea4c505 + React-jserrorhandler: 2975ad2bf99de61e2f911fe4ba00c6edfcba2127 + React-jsi: 3502d2ca39504605486f391eb3e2e915f673eb7a + React-jsiexecutor: 09b3acc1dde3b63d2d08fa17191463abbc1bd122 + React-jsinspector: 388a1e6c34bb3ca512f0dcffbcd5629ef885ef07 + React-logger: a4e652d70b69d6a504206151b6716b2a1d2b06c6 + React-Mapbuffer: ae15244a7453f2a7dc940bb5a284219eda5efd8d + React-nativeconfig: ff20036bdb830e428156d56c8bc9f6ffdd77fcbf + React-NativeModulesApple: 0b8b78b6faca644cdf0cf46247581d480d89bd39 + React-perflogger: eebd121b64074b2dd4097b69f07444e743c643ae + React-RCTActionSheet: 4908b3de0a10325f46ec18d1dee238c050971b32 + React-RCTAnimation: 5c9e54b46f760570c9fd89720f79a47bf4a84cd7 + React-RCTAppDelegate: 12539ef46a7e27eead75226a5ffe8e955a13f1bd + React-RCTBlob: df939c85cb341077b9f7ab4477884f53c567259e + React-RCTFabric: d6451d54f7a2a1ff6bb82af15e506fd9f7ac81c5 + React-RCTImage: fb5572d48551f66841d709145af0e69fe5b1dba3 + React-RCTLinking: 93e130562d46db1fb3ae5cd97a284a3ec4af1580 + React-RCTNetwork: f4db24ee7458b84e610d4b6df0f45e62c9b78a4b + React-RCTPushNotification: e51ae9931f3402a431b9d0d1e95dfdc9a3da2346 + React-RCTSettings: 870f618220833822c3f4adc1f45a081c0cf49177 + React-RCTTest: 5aa96c542230ef61a358dbcb524ca836ab52510d + React-RCTText: 40e8035457ac6c47a363f48738039fe782d594f0 + React-RCTVibration: ccaca5ddb4b9c710b438a9eb7a77b6967b41d9a4 + React-rendererdebug: d1a273d4d901eda2626ce131fcb80cf0fb6e0bd4 + React-rncore: acbd954ffd25c8b8e0dacc0fadb4c9321a669cb8 + React-runtimeexecutor: 6af4638c47dc089d2aa9d3c7284d0a427b46e14e + React-runtimescheduler: 4becb77475d13f1234ed27cb7c8ad17b0bdecccc + React-utils: 8c9ad3dacf67c84a72b199dccdac1a4cd54771fc + ReactCommon: c0dd48866edf79c26d52ed92443a6d3273206805 + ReactCommon-Samples: acde48094eb396ee8f7e9a51d5fcd90cd6122bbc + ScreenshotManager: b2b5732286c9d80ceca6a8576c4fd9f619608d96 SocketRocket: f6c6249082c011e6de2de60ed641ef8bbe0cfac9 - Yoga: 1e5cd719d4dd69cceda8456c7059aa064d35a107 + Yoga: b1985d78d08f08556a186a22d9629fa22fd5e5cf PODFILE CHECKSUM: b1a8404bc4b61ed54677e78b8e2f8269707283c7