1/* 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8#import "ABI47_0_0RCTRedBox.h" 9 10#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h> 11#import <ABI47_0_0React/ABI47_0_0RCTBridge.h> 12#import <ABI47_0_0React/ABI47_0_0RCTConvert.h> 13#import <ABI47_0_0React/ABI47_0_0RCTDefines.h> 14#import <ABI47_0_0React/ABI47_0_0RCTErrorInfo.h> 15#import <ABI47_0_0React/ABI47_0_0RCTEventDispatcherProtocol.h> 16#import <ABI47_0_0React/ABI47_0_0RCTJSStackFrame.h> 17#import <ABI47_0_0React/ABI47_0_0RCTRedBoxExtraDataViewController.h> 18#import <ABI47_0_0React/ABI47_0_0RCTRedBoxSetEnabled.h> 19#import <ABI47_0_0React/ABI47_0_0RCTReloadCommand.h> 20#import <ABI47_0_0React/ABI47_0_0RCTUtils.h> 21 22#import <objc/runtime.h> 23 24#import "ABI47_0_0CoreModulesPlugins.h" 25 26#if ABI47_0_0RCT_DEV_MENU 27 28@class ABI47_0_0RCTRedBoxWindow; 29 30@interface UIButton (ABI47_0_0RCTRedBox) 31 32@property (nonatomic) ABI47_0_0RCTRedBoxButtonPressHandler rct_handler; 33 34- (void)rct_addBlock:(ABI47_0_0RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents; 35 36@end 37 38@implementation UIButton (ABI47_0_0RCTRedBox) 39 40- (ABI47_0_0RCTRedBoxButtonPressHandler)rct_handler 41{ 42 return objc_getAssociatedObject(self, @selector(rct_handler)); 43} 44 45- (void)setRct_handler:(ABI47_0_0RCTRedBoxButtonPressHandler)rct_handler 46{ 47 objc_setAssociatedObject(self, @selector(rct_handler), rct_handler, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 48} 49 50- (void)rct_callBlock 51{ 52 if (self.rct_handler) { 53 self.rct_handler(); 54 } 55} 56 57- (void)rct_addBlock:(ABI47_0_0RCTRedBoxButtonPressHandler)handler forControlEvents:(UIControlEvents)controlEvents 58{ 59 self.rct_handler = handler; 60 [self addTarget:self action:@selector(rct_callBlock) forControlEvents:controlEvents]; 61} 62 63@end 64 65@protocol ABI47_0_0RCTRedBoxWindowActionDelegate <NSObject> 66 67- (void)redBoxWindow:(ABI47_0_0RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(ABI47_0_0RCTJSStackFrame *)stackFrame; 68- (void)reloadFromRedBoxWindow:(ABI47_0_0RCTRedBoxWindow *)redBoxWindow; 69- (void)loadExtraDataViewController; 70 71@end 72 73@interface ABI47_0_0RCTRedBoxWindow : NSObject <UITableViewDelegate, UITableViewDataSource> 74@property (nonatomic, strong) UIViewController *rootViewController; 75@property (nonatomic, weak) id<ABI47_0_0RCTRedBoxWindowActionDelegate> actionDelegate; 76@end 77 78@implementation ABI47_0_0RCTRedBoxWindow { 79 UITableView *_stackTraceTableView; 80 NSString *_lastErrorMessage; 81 NSArray<ABI47_0_0RCTJSStackFrame *> *_lastStackTrace; 82 int _lastErrorCookie; 83} 84 85- (instancetype)initWithFrame:(CGRect)frame 86 customButtonTitles:(NSArray<NSString *> *)customButtonTitles 87 customButtonHandlers:(NSArray<ABI47_0_0RCTRedBoxButtonPressHandler> *)customButtonHandlers 88{ 89 if (self = [super init]) { 90 _lastErrorCookie = -1; 91 92 _rootViewController = [UIViewController new]; 93 UIView *rootView = _rootViewController.view; 94 rootView.frame = frame; 95 rootView.backgroundColor = [UIColor blackColor]; 96 97 const CGFloat buttonHeight = 60; 98 99 CGRect detailsFrame = rootView.bounds; 100 detailsFrame.size.height -= buttonHeight + (double)[self bottomSafeViewHeight]; 101 102 _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; 103 _stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 104 _stackTraceTableView.delegate = self; 105 _stackTraceTableView.dataSource = self; 106 _stackTraceTableView.backgroundColor = [UIColor clearColor]; 107 _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3]; 108 _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; 109 _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; 110 [rootView addSubview:_stackTraceTableView]; 111 112#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST 113 NSString *reloadText = @"Reload\n(\u2318R)"; 114 NSString *dismissText = @"Dismiss\n(ESC)"; 115 NSString *copyText = @"Copy\n(\u2325\u2318C)"; 116 NSString *extraText = @"Extra Info\n(\u2318E)"; 117#else 118 NSString *reloadText = @"Reload JS"; 119 NSString *dismissText = @"Dismiss"; 120 NSString *copyText = @"Copy"; 121 NSString *extraText = @"Extra Info"; 122#endif 123 124 UIButton *dismissButton = [self redBoxButton:dismissText 125 accessibilityIdentifier:@"redbox-dismiss" 126 selector:@selector(dismiss) 127 block:nil]; 128 UIButton *reloadButton = [self redBoxButton:reloadText 129 accessibilityIdentifier:@"redbox-reload" 130 selector:@selector(reload) 131 block:nil]; 132 UIButton *copyButton = [self redBoxButton:copyText 133 accessibilityIdentifier:@"redbox-copy" 134 selector:@selector(copyStack) 135 block:nil]; 136 UIButton *extraButton = [self redBoxButton:extraText 137 accessibilityIdentifier:@"redbox-extra" 138 selector:@selector(showExtraDataViewController) 139 block:nil]; 140 141 CGFloat buttonWidth = frame.size.width / (CGFloat)(4 + [customButtonTitles count]); 142 CGFloat bottomButtonHeight = frame.size.height - buttonHeight - (CGFloat)[self bottomSafeViewHeight]; 143 dismissButton.frame = CGRectMake(0, bottomButtonHeight, buttonWidth, buttonHeight); 144 reloadButton.frame = CGRectMake(buttonWidth, bottomButtonHeight, buttonWidth, buttonHeight); 145 copyButton.frame = CGRectMake(buttonWidth * 2, bottomButtonHeight, buttonWidth, buttonHeight); 146 extraButton.frame = CGRectMake(buttonWidth * 3, bottomButtonHeight, buttonWidth, buttonHeight); 147 148 [rootView addSubview:dismissButton]; 149 [rootView addSubview:reloadButton]; 150 [rootView addSubview:copyButton]; 151 [rootView addSubview:extraButton]; 152 153 for (NSUInteger i = 0; i < [customButtonTitles count]; i++) { 154 UIButton *button = [self redBoxButton:customButtonTitles[i] 155 accessibilityIdentifier:@"" 156 selector:nil 157 block:customButtonHandlers[i]]; 158 button.frame = CGRectMake(buttonWidth * (double)(4 + i), bottomButtonHeight, buttonWidth, buttonHeight); 159 [rootView addSubview:button]; 160 } 161 162 UIView *topBorder = 163 [[UIView alloc] initWithFrame:CGRectMake(0, bottomButtonHeight + 1, rootView.frame.size.width, 1)]; 164 topBorder.backgroundColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; 165 166 [rootView addSubview:topBorder]; 167 168 UIView *bottomSafeView = [UIView new]; 169 bottomSafeView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1]; 170 bottomSafeView.frame = CGRectMake( 171 0, 172 frame.size.height - (CGFloat)[self bottomSafeViewHeight], 173 frame.size.width, 174 (CGFloat)[self bottomSafeViewHeight]); 175 176 [rootView addSubview:bottomSafeView]; 177 } 178 return self; 179} 180 181- (UIButton *)redBoxButton:(NSString *)title 182 accessibilityIdentifier:(NSString *)accessibilityIdentifier 183 selector:(SEL)selector 184 block:(ABI47_0_0RCTRedBoxButtonPressHandler)block 185{ 186 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 187 button.autoresizingMask = 188 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin; 189 button.accessibilityIdentifier = accessibilityIdentifier; 190 button.titleLabel.font = [UIFont systemFontOfSize:13]; 191 button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; 192 button.titleLabel.textAlignment = NSTextAlignmentCenter; 193 button.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1]; 194 [button setTitle:title forState:UIControlStateNormal]; 195 [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 196 [button setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateHighlighted]; 197 if (selector) { 198 [button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside]; 199 } else if (block) { 200 [button rct_addBlock:block forControlEvents:UIControlEventTouchUpInside]; 201 } 202 return button; 203} 204 205- (NSInteger)bottomSafeViewHeight 206{ 207 return ABI47_0_0RCTSharedApplication().delegate.window.safeAreaInsets.bottom; 208} 209 210ABI47_0_0RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) 211 212- (NSString *)stripAnsi:(NSString *)text 213{ 214 NSError *error = nil; 215 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\x1b\\[[0-9;]*m" 216 options:NSRegularExpressionCaseInsensitive 217 error:&error]; 218 return [regex stringByReplacingMatchesInString:text options:0 range:NSMakeRange(0, [text length]) withTemplate:@""]; 219} 220 221- (void)showErrorMessage:(NSString *)message 222 withStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 223 isUpdate:(BOOL)isUpdate 224 errorCookie:(int)errorCookie 225{ 226 // Remove ANSI color codes from the message 227 NSString *messageWithoutAnsi = [self stripAnsi:message]; 228 229 BOOL isRootViewControllerPresented = self.rootViewController.presentingViewController != nil; 230 // Show if this is a new message, or if we're updating the previous message 231 BOOL isNew = !isRootViewControllerPresented && !isUpdate; 232 BOOL isUpdateForSameMessage = !isNew && 233 (isRootViewControllerPresented && isUpdate && 234 ((errorCookie == -1 && [_lastErrorMessage isEqualToString:messageWithoutAnsi]) || 235 (errorCookie == _lastErrorCookie))); 236 if (isNew || isUpdateForSameMessage) { 237 _lastStackTrace = stack; 238 // message is displayed using UILabel, which is unable to render text of 239 // unlimited length, so we truncate it 240 _lastErrorMessage = [messageWithoutAnsi substringToIndex:MIN((NSUInteger)10000, messageWithoutAnsi.length)]; 241 _lastErrorCookie = errorCookie; 242 243 [_stackTraceTableView reloadData]; 244 245 if (!isRootViewControllerPresented) { 246 [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] 247 atScrollPosition:UITableViewScrollPositionTop 248 animated:NO]; 249 [ABI47_0_0RCTKeyWindow().rootViewController presentViewController:self.rootViewController animated:YES completion:nil]; 250 } 251 } 252} 253 254- (void)dismiss 255{ 256 [self.rootViewController dismissViewControllerAnimated:YES completion:nil]; 257} 258 259- (void)reload 260{ 261 [_actionDelegate reloadFromRedBoxWindow:self]; 262} 263 264- (void)showExtraDataViewController 265{ 266 [_actionDelegate loadExtraDataViewController]; 267} 268 269- (void)copyStack 270{ 271 NSMutableString *fullStackTrace; 272 273 if (_lastErrorMessage != nil) { 274 fullStackTrace = [_lastErrorMessage mutableCopy]; 275 [fullStackTrace appendString:@"\n\n"]; 276 } else { 277 fullStackTrace = [NSMutableString string]; 278 } 279 280 for (ABI47_0_0RCTJSStackFrame *stackFrame in _lastStackTrace) { 281 [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]]; 282 if (stackFrame.file) { 283 [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]]; 284 } 285 } 286 UIPasteboard *pb = [UIPasteboard generalPasteboard]; 287 [pb setString:fullStackTrace]; 288} 289 290- (NSString *)formatFrameSource:(ABI47_0_0RCTJSStackFrame *)stackFrame 291{ 292 NSString *fileName = ABI47_0_0RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @"<unknown file>"; 293 NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld", fileName, (long long)stackFrame.lineNumber]; 294 295 if (stackFrame.column != 0) { 296 lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column]; 297 } 298 return lineInfo; 299} 300 301#pragma mark - TableView 302 303- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView 304{ 305 return 2; 306} 307 308- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section 309{ 310 return section == 0 ? 1 : _lastStackTrace.count; 311} 312 313- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 314{ 315 if (indexPath.section == 0) { 316 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"]; 317 return [self reuseCell:cell forErrorMessage:_lastErrorMessage]; 318 } 319 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 320 NSUInteger index = indexPath.row; 321 ABI47_0_0RCTJSStackFrame *stackFrame = _lastStackTrace[index]; 322 return [self reuseCell:cell forStackFrame:stackFrame]; 323} 324 325- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message 326{ 327 if (!cell) { 328 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"msg-cell"]; 329 cell.textLabel.accessibilityIdentifier = @"redbox-error"; 330 cell.textLabel.textColor = [UIColor whiteColor]; 331 cell.textLabel.font = [UIFont boldSystemFontOfSize:16]; 332 cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; 333 cell.textLabel.numberOfLines = 0; 334 cell.detailTextLabel.textColor = [UIColor whiteColor]; 335 cell.backgroundColor = [UIColor colorWithRed:0.82 green:0.10 blue:0.15 alpha:1.0]; 336 cell.selectionStyle = UITableViewCellSelectionStyleNone; 337 } 338 339 cell.textLabel.text = message; 340 341 return cell; 342} 343 344- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(ABI47_0_0RCTJSStackFrame *)stackFrame 345{ 346 if (!cell) { 347 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; 348 cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14]; 349 cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping; 350 cell.textLabel.numberOfLines = 2; 351 cell.detailTextLabel.textColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; 352 cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11]; 353 cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; 354 cell.backgroundColor = [UIColor clearColor]; 355 cell.selectedBackgroundView = [UIView new]; 356 cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2]; 357 } 358 359 cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)"; 360 if (stackFrame.file) { 361 cell.detailTextLabel.text = [self formatFrameSource:stackFrame]; 362 } else { 363 cell.detailTextLabel.text = @""; 364 } 365 cell.textLabel.textColor = stackFrame.collapse ? [UIColor lightGrayColor] : [UIColor whiteColor]; 366 cell.detailTextLabel.textColor = stackFrame.collapse ? [UIColor colorWithRed:0.50 green:0.50 blue:0.50 alpha:1.0] 367 : [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; 368 return cell; 369} 370 371- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 372{ 373 if (indexPath.section == 0) { 374 NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 375 paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; 376 377 NSDictionary *attributes = 378 @{NSFontAttributeName : [UIFont boldSystemFontOfSize:16], NSParagraphStyleAttributeName : paragraphStyle}; 379 CGRect boundingRect = 380 [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) 381 options:NSStringDrawingUsesLineFragmentOrigin 382 attributes:attributes 383 context:nil]; 384 return ceil(boundingRect.size.height) + 40; 385 } else { 386 return 50; 387 } 388} 389 390- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 391{ 392 if (indexPath.section == 1) { 393 NSUInteger row = indexPath.row; 394 ABI47_0_0RCTJSStackFrame *stackFrame = _lastStackTrace[row]; 395 [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame]; 396 } 397 [tableView deselectRowAtIndexPath:indexPath animated:YES]; 398} 399 400#pragma mark - Key commands 401 402- (NSArray<UIKeyCommand *> *)keyCommands 403{ 404 // NOTE: We could use ABI47_0_0RCTKeyCommands for this, but since 405 // we control this window, we can use the standard, non-hacky 406 // mechanism instead 407 408 return @[ 409 // Dismiss red box 410 [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(dismiss)], 411 412 // Reload 413 [UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:@selector(reload)], 414 415 // Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from 416 // the simulator to the desktop pasteboard. 417 [UIKeyCommand keyCommandWithInput:@"c" 418 modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate 419 action:@selector(copyStack)], 420 421 // Extra data 422 [UIKeyCommand keyCommandWithInput:@"e" 423 modifierFlags:UIKeyModifierCommand 424 action:@selector(showExtraDataViewController)] 425 ]; 426} 427 428- (BOOL)canBecomeFirstResponder 429{ 430 return YES; 431} 432 433@end 434 435@interface ABI47_0_0RCTRedBox () < 436 ABI47_0_0RCTInvalidating, 437 ABI47_0_0RCTRedBoxWindowActionDelegate, 438 ABI47_0_0RCTRedBoxExtraDataActionDelegate, 439 ABI47_0_0NativeRedBoxSpec> 440@end 441 442@implementation ABI47_0_0RCTRedBox { 443 ABI47_0_0RCTRedBoxWindow *_window; 444 NSMutableArray<id<ABI47_0_0RCTErrorCustomizer>> *_errorCustomizers; 445 ABI47_0_0RCTRedBoxExtraDataViewController *_extraDataViewController; 446 NSMutableArray<NSString *> *_customButtonTitles; 447 NSMutableArray<ABI47_0_0RCTRedBoxButtonPressHandler> *_customButtonHandlers; 448} 449 450@synthesize bridge = _bridge; 451@synthesize moduleRegistry = _moduleRegistry; 452@synthesize bundleManager = _bundleManager; 453 454ABI47_0_0RCT_EXPORT_MODULE() 455 456- (void)registerErrorCustomizer:(id<ABI47_0_0RCTErrorCustomizer>)errorCustomizer 457{ 458 dispatch_async(dispatch_get_main_queue(), ^{ 459 if (!self->_errorCustomizers) { 460 self->_errorCustomizers = [NSMutableArray array]; 461 } 462 if (![self->_errorCustomizers containsObject:errorCustomizer]) { 463 [self->_errorCustomizers addObject:errorCustomizer]; 464 } 465 }); 466} 467 468// WARNING: Should only be called from the main thread/dispatch queue. 469- (ABI47_0_0RCTErrorInfo *)_customizeError:(ABI47_0_0RCTErrorInfo *)error 470{ 471 ABI47_0_0RCTAssertMainQueue(); 472 if (!self->_errorCustomizers) { 473 return error; 474 } 475 for (id<ABI47_0_0RCTErrorCustomizer> customizer in self->_errorCustomizers) { 476 ABI47_0_0RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error]; 477 if (newInfo) { 478 error = newInfo; 479 } 480 } 481 return error; 482} 483 484- (void)showError:(NSError *)error 485{ 486 [self showErrorMessage:error.localizedDescription 487 withDetails:error.localizedFailureReason 488 stack:error.userInfo[ABI47_0_0RCTJSStackTraceKey] 489 errorCookie:-1]; 490} 491 492- (void)showErrorMessage:(NSString *)message 493{ 494 [self showErrorMessage:message withParsedStack:nil isUpdate:NO errorCookie:-1]; 495} 496 497- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details 498{ 499 [self showErrorMessage:message withDetails:details stack:nil errorCookie:-1]; 500} 501 502- (void)showErrorMessage:(NSString *)message 503 withDetails:(NSString *)details 504 stack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 505 errorCookie:(int)errorCookie 506{ 507 NSString *combinedMessage = message; 508 if (details) { 509 combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details]; 510 } 511 [self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO errorCookie:errorCookie]; 512} 513 514- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack 515{ 516 [self showErrorMessage:message withRawStack:rawStack errorCookie:-1]; 517} 518 519- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack errorCookie:(int)errorCookie 520{ 521 NSArray<ABI47_0_0RCTJSStackFrame *> *stack = [ABI47_0_0RCTJSStackFrame stackFramesWithLines:rawStack]; 522 [self showErrorMessage:message withParsedStack:stack isUpdate:NO errorCookie:errorCookie]; 523} 524 525- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack 526{ 527 [self showErrorMessage:message withStack:stack errorCookie:-1]; 528} 529 530- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack 531{ 532 [self updateErrorMessage:message withStack:stack errorCookie:-1]; 533} 534 535- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie 536{ 537 [self showErrorMessage:message 538 withParsedStack:[ABI47_0_0RCTJSStackFrame stackFramesWithDictionaries:stack] 539 isUpdate:NO 540 errorCookie:errorCookie]; 541} 542 543- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie 544{ 545 [self showErrorMessage:message 546 withParsedStack:[ABI47_0_0RCTJSStackFrame stackFramesWithDictionaries:stack] 547 isUpdate:YES 548 errorCookie:errorCookie]; 549} 550 551- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 552{ 553 [self showErrorMessage:message withParsedStack:stack errorCookie:-1]; 554} 555 556- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 557{ 558 [self updateErrorMessage:message withParsedStack:stack errorCookie:-1]; 559} 560 561- (void)showErrorMessage:(NSString *)message 562 withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 563 errorCookie:(int)errorCookie 564{ 565 [self showErrorMessage:message withParsedStack:stack isUpdate:NO errorCookie:errorCookie]; 566} 567 568- (void)updateErrorMessage:(NSString *)message 569 withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 570 errorCookie:(int)errorCookie 571{ 572 [self showErrorMessage:message withParsedStack:stack isUpdate:YES errorCookie:errorCookie]; 573} 574 575- (void)showErrorMessage:(NSString *)message 576 withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 577 isUpdate:(BOOL)isUpdate 578 errorCookie:(int)errorCookie 579{ 580 dispatch_async(dispatch_get_main_queue(), ^{ 581 if (self->_extraDataViewController == nil) { 582 self->_extraDataViewController = [ABI47_0_0RCTRedBoxExtraDataViewController new]; 583 self->_extraDataViewController.actionDelegate = self; 584 } 585 586#pragma clang diagnostic push 587#pragma clang diagnostic ignored "-Wdeprecated-declarations" 588 [[self->_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"collectRedBoxExtraData" 589 body:nil]; 590#pragma clang diagnostic pop 591 592 if (!self->_window) { 593 self->_window = [[ABI47_0_0RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds 594 customButtonTitles:self->_customButtonTitles 595 customButtonHandlers:self->_customButtonHandlers]; 596 self->_window.actionDelegate = self; 597 } 598 599 ABI47_0_0RCTErrorInfo *errorInfo = [[ABI47_0_0RCTErrorInfo alloc] initWithErrorMessage:message stack:stack]; 600 errorInfo = [self _customizeError:errorInfo]; 601 [self->_window showErrorMessage:errorInfo.errorMessage 602 withStack:errorInfo.stack 603 isUpdate:isUpdate 604 errorCookie:errorCookie]; 605 }); 606} 607 608- (void)loadExtraDataViewController 609{ 610 dispatch_async(dispatch_get_main_queue(), ^{ 611 // Make sure the CMD+E shortcut doesn't call this twice 612 if (self->_extraDataViewController != nil && ![self->_window.rootViewController presentedViewController]) { 613 [self->_window.rootViewController presentViewController:self->_extraDataViewController 614 animated:YES 615 completion:nil]; 616 } 617 }); 618} 619 620ABI47_0_0RCT_EXPORT_METHOD(setExtraData : (NSDictionary *)extraData forIdentifier : (NSString *)identifier) 621{ 622 [_extraDataViewController addExtraData:extraData forIdentifier:identifier]; 623} 624 625ABI47_0_0RCT_EXPORT_METHOD(dismiss) 626{ 627 dispatch_async(dispatch_get_main_queue(), ^{ 628 [self->_window dismiss]; 629 }); 630} 631 632- (void)invalidate 633{ 634 [self dismiss]; 635} 636 637- (void)redBoxWindow:(__unused ABI47_0_0RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(ABI47_0_0RCTJSStackFrame *)stackFrame 638{ 639 NSURL *const bundleURL = _overrideBundleURL ?: _bundleManager.bundleURL; 640 if (![bundleURL.scheme hasPrefix:@"http"]) { 641 ABI47_0_0RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager."); 642 return; 643 } 644 645 NSData *stackFrameJSON = [ABI47_0_0RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding]; 646 NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length]; 647 NSMutableURLRequest *request = [NSMutableURLRequest new]; 648 request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL]; 649 request.HTTPMethod = @"POST"; 650 request.HTTPBody = stackFrameJSON; 651 [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; 652 [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 653 654 [[[NSURLSession sharedSession] dataTaskWithRequest:request] resume]; 655} 656 657- (void)reload 658{ 659 // Window is not used and can be nil 660 [self reloadFromRedBoxWindow:nil]; 661} 662 663- (void)reloadFromRedBoxWindow:(__unused ABI47_0_0RCTRedBoxWindow *)redBoxWindow 664{ 665 if (_overrideReloadAction) { 666 _overrideReloadAction(); 667 } else { 668 ABI47_0_0RCTTriggerReloadCommandListeners(@"Redbox"); 669 } 670 [self dismiss]; 671} 672 673- (void)addCustomButton:(NSString *)title onPressHandler:(ABI47_0_0RCTRedBoxButtonPressHandler)handler 674{ 675 if (!_customButtonTitles) { 676 _customButtonTitles = [NSMutableArray new]; 677 _customButtonHandlers = [NSMutableArray new]; 678 } 679 680 [_customButtonTitles addObject:title]; 681 [_customButtonHandlers addObject:handler]; 682} 683 684- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule: 685 (const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params 686{ 687 return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeRedBoxSpecJSI>(params); 688} 689 690@end 691 692@implementation ABI47_0_0RCTBridge (ABI47_0_0RCTRedBox) 693 694- (ABI47_0_0RCTRedBox *)redBox 695{ 696 return ABI47_0_0RCTRedBoxGetEnabled() ? [self moduleForClass:[ABI47_0_0RCTRedBox class]] : nil; 697} 698 699@end 700 701#else // Disabled 702 703@interface ABI47_0_0RCTRedBox () <ABI47_0_0NativeRedBoxSpec> 704@end 705 706@implementation ABI47_0_0RCTRedBox 707 708+ (NSString *)moduleName 709{ 710 return nil; 711} 712- (void)registerErrorCustomizer:(id<ABI47_0_0RCTErrorCustomizer>)errorCustomizer 713{ 714} 715- (void)showError:(NSError *)error 716{ 717} 718- (void)showErrorMessage:(NSString *)message 719{ 720} 721- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details 722{ 723} 724- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack 725{ 726} 727- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack errorCookie:(int)errorCookie 728{ 729} 730- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack 731{ 732} 733- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack 734{ 735} 736- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie 737{ 738} 739- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack errorCookie:(int)errorCookie 740{ 741} 742- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 743{ 744} 745- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 746{ 747} 748- (void)showErrorMessage:(NSString *)message 749 withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 750 errorCookie:(int)errorCookie 751{ 752} 753- (void)updateErrorMessage:(NSString *)message 754 withParsedStack:(NSArray<ABI47_0_0RCTJSStackFrame *> *)stack 755 errorCookie:(int)errorCookie 756{ 757} 758- (void)setExtraData:(NSDictionary *)extraData forIdentifier:(NSString *)identifier 759{ 760} 761 762- (void)dismiss 763{ 764} 765 766- (void)addCustomButton:(NSString *)title onPressHandler:(ABI47_0_0RCTRedBoxButtonPressHandler)handler 767{ 768} 769- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule: 770 (const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params 771{ 772 return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeRedBoxSpecJSI>(params); 773} 774 775@end 776 777@implementation ABI47_0_0RCTBridge (ABI47_0_0RCTRedBox) 778 779- (ABI47_0_0RCTRedBox *)redBox 780{ 781 return nil; 782} 783 784@end 785 786#endif 787 788Class ABI47_0_0RCTRedBoxCls(void) 789{ 790 return ABI47_0_0RCTRedBox.class; 791} 792