mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-25 21:05:40 +08:00
Converted RCTRedBox to a bridge module
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
NSUInteger _reloadRetries;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate
|
||||
@@ -44,7 +46,7 @@ RCT_EXPORT_METHOD(reportSoftException:(NSString *)message
|
||||
[_delegate handleSoftJSExceptionWithMessage:message stack:stack];
|
||||
return;
|
||||
}
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||
[_bridge.redBox showErrorMessage:message withStack:stack];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(reportFatalException:(NSString *)message
|
||||
@@ -56,7 +58,7 @@ RCT_EXPORT_METHOD(reportFatalException:(NSString *)message
|
||||
return;
|
||||
}
|
||||
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||
[_bridge.redBox showErrorMessage:message withStack:stack];
|
||||
|
||||
if (!RCT_DEBUG) {
|
||||
|
||||
@@ -95,7 +97,7 @@ RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message
|
||||
return;
|
||||
}
|
||||
|
||||
[[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack];
|
||||
[_bridge.redBox updateErrorMessage:message withStack:stack];
|
||||
}
|
||||
|
||||
// Deprecated. Use reportFatalException directly instead.
|
||||
|
||||
35
React/Modules/RCTRedBox.h
Normal file
35
React/Modules/RCTRedBox.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTRedBox : NSObject <RCTBridgeModule>
|
||||
|
||||
- (void)showError:(NSError *)error;
|
||||
- (void)showErrorMessage:(NSString *)message;
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details;
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack;
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack;
|
||||
|
||||
- (void)dismiss;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This category makes the red box instance available via the RCTBridge, which
|
||||
* is useful for any class that needs to access the red box or error log.
|
||||
*/
|
||||
@interface RCTBridge (RCTRedBox)
|
||||
|
||||
@property (nonatomic, readonly) RCTRedBox *redBox;
|
||||
|
||||
@end
|
||||
350
React/Modules/RCTRedBox.m
Normal file
350
React/Modules/RCTRedBox.m
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#if RCT_DEBUG
|
||||
|
||||
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@property (nonatomic, copy) NSString *lastErrorMessage;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRedBoxWindow
|
||||
{
|
||||
UIView *_rootView;
|
||||
UITableView *_stackTraceTableView;
|
||||
|
||||
NSArray *_lastStackTrace;
|
||||
|
||||
UITableViewCell *_cachedMessageCell;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
self.windowLevel = UIWindowLevelAlert + 1000;
|
||||
self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
|
||||
self.hidden = YES;
|
||||
|
||||
UIViewController *rootController = [UIViewController new];
|
||||
self.rootViewController = rootController;
|
||||
_rootView = rootController.view;
|
||||
_rootView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
const CGFloat buttonHeight = 60;
|
||||
|
||||
CGRect detailsFrame = _rootView.bounds;
|
||||
detailsFrame.size.height -= buttonHeight;
|
||||
|
||||
_stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
|
||||
_stackTraceTableView.delegate = self;
|
||||
_stackTraceTableView.dataSource = self;
|
||||
_stackTraceTableView.backgroundColor = [UIColor clearColor];
|
||||
_stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3];
|
||||
_stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
[_rootView addSubview:_stackTraceTableView];
|
||||
|
||||
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
dismissButton.accessibilityIdentifier = @"redbox-dismiss";
|
||||
dismissButton.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
[dismissButton setTitle:@"Dismiss (ESC)" forState:UIControlStateNormal];
|
||||
[dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
|
||||
[dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
|
||||
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
reloadButton.accessibilityIdentifier = @"redbox-reload";
|
||||
reloadButton.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
[reloadButton setTitle:@"Reload JS (\u2318R)" forState:UIControlStateNormal];
|
||||
[reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
|
||||
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
|
||||
[reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
CGFloat buttonWidth = self.bounds.size.width / 2;
|
||||
dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
[_rootView addSubview:dismissButton];
|
||||
[_rootView addSubview:reloadButton];
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(dismiss)
|
||||
name:RCTReloadNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)openStackFrameInEditor:(NSDictionary *)stackFrame
|
||||
{
|
||||
NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[stackFrameJSON length]];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest new];
|
||||
request.URL = [RCTConvert NSURL:@"http://localhost:8081/open-stack-frame"];
|
||||
request.HTTPMethod = @"POST";
|
||||
request.HTTPBody = stackFrameJSON;
|
||||
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) {
|
||||
_lastStackTrace = stack;
|
||||
_lastErrorMessage = message;
|
||||
|
||||
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
|
||||
[_stackTraceTableView reloadData];
|
||||
[_stackTraceTableView setNeedsLayout];
|
||||
|
||||
if (self.hidden) {
|
||||
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
|
||||
atScrollPosition:UITableViewScrollPositionTop
|
||||
animated:NO];
|
||||
}
|
||||
|
||||
[self makeKeyAndVisible];
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
self.hidden = YES;
|
||||
[self resignFirstResponder];
|
||||
[[[[UIApplication sharedApplication] delegate] window] makeKeyWindow];
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil userInfo:nil];
|
||||
}
|
||||
|
||||
#pragma mark - TableView
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return section == 0 ? 1 : [_lastStackTrace count];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 0) {
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
|
||||
return [self reuseCell:cell forErrorMessage:_lastErrorMessage];
|
||||
}
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
|
||||
NSUInteger index = [indexPath row];
|
||||
NSDictionary *stackFrame = _lastStackTrace[index];
|
||||
return [self reuseCell:cell forStackFrame:stackFrame];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message
|
||||
{
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"];
|
||||
cell.textLabel.accessibilityIdentifier = @"redbox-error";
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
cell.textLabel.numberOfLines = 0;
|
||||
cell.detailTextLabel.textColor = [UIColor whiteColor];
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
cell.textLabel.text = message;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(NSDictionary *)stackFrame
|
||||
{
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
|
||||
cell.textLabel.textColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||
cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
|
||||
cell.textLabel.numberOfLines = 2;
|
||||
cell.detailTextLabel.textColor = [UIColor colorWithWhite:1 alpha:0.7];
|
||||
cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11];
|
||||
cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.selectedBackgroundView = [UIView new];
|
||||
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
|
||||
}
|
||||
|
||||
cell.textLabel.text = stackFrame[@"methodName"];
|
||||
|
||||
NSString *fileAndLine = [stackFrame[@"file"] lastPathComponent];
|
||||
if (fileAndLine) {
|
||||
fileAndLine = [fileAndLine stringByAppendingFormat:@":%@", stackFrame[@"lineNumber"]];
|
||||
cell.detailTextLabel.text = fileAndLine;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 0) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
|
||||
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16],
|
||||
NSParagraphStyleAttributeName: paragraphStyle};
|
||||
CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
|
||||
return ceil(boundingRect.size.height) + 40;
|
||||
} else {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 1) {
|
||||
NSUInteger row = [indexPath row];
|
||||
NSDictionary *stackFrame = _lastStackTrace[row];
|
||||
[self openStackFrameInEditor:stackFrame];
|
||||
}
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Key commands
|
||||
|
||||
- (NSArray *)keyCommands
|
||||
{
|
||||
// NOTE: We could use RCTKeyCommands for this, but since
|
||||
// we control this window, we can use the standard, non-hacky
|
||||
// mechanism instead
|
||||
|
||||
return @[
|
||||
|
||||
// Dismiss red box
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
|
||||
modifierFlags:0
|
||||
action:@selector(dismiss)],
|
||||
|
||||
// Reload
|
||||
[UIKeyCommand keyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:@selector(reload)]
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRedBox
|
||||
{
|
||||
RCTRedBoxWindow *_window;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)showError:(NSError *)error
|
||||
{
|
||||
[self showErrorMessage:error.localizedDescription withDetails:error.localizedFailureReason];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message
|
||||
{
|
||||
[self showErrorMessage:message withStack:nil showIfHidden:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
|
||||
{
|
||||
NSString *combinedMessage = message;
|
||||
if (details) {
|
||||
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
|
||||
}
|
||||
[self showErrorMessage:combinedMessage];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:YES];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!_window) {
|
||||
_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
}
|
||||
[_window showErrorMessage:message withStack:stack showIfHidden:shouldShow];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
[_window dismiss];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTRedBox)
|
||||
|
||||
- (RCTRedBox *)redBox
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTRedBox class])];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#else // Disabled
|
||||
|
||||
@implementation RCTRedBox
|
||||
|
||||
- (void)showError:(NSError *)message {}
|
||||
- (void)showErrorMessage:(NSString *)message {}
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details {}
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack {}
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack {}
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow {}
|
||||
- (void)dismiss {}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTRedBox)
|
||||
|
||||
- (RCTRedBox *)redBox { return nil; }
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user