Skip to content

Commit

Permalink
Redo error handling on iOS
Browse files Browse the repository at this point in the history
Reviewed By: danzimm

Differential Revision: D5969343

fbshipit-source-id: 376984a6e959349260c54884c0b0b719f4c353d6
  • Loading branch information
adamjernst authored and facebook-github-bot committed Oct 5, 2017
1 parent 1e3a8e2 commit e87904c
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 29 deletions.
8 changes: 5 additions & 3 deletions React/Base/RCTJSStackFrame.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
static NSRegularExpression *_regex;
dispatch_once(&onceToken, ^{
NSError *regexError;
_regex = [NSRegularExpression regularExpressionWithPattern:@"^([^@]+)@(.*):(\\d+):(\\d+)$" options:0 error:&regexError];
_regex = [NSRegularExpression regularExpressionWithPattern:@"^(?:([^@]+)@)?(.*):(\\d+):(\\d+)$" options:0 error:&regexError];
if (regexError) {
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
}
Expand Down Expand Up @@ -56,7 +56,9 @@ + (instancetype)stackFrameWithLine:(NSString *)line
return nil;
}

NSString *methodName = [line substringWithRange:[match rangeAtIndex:1]];
// methodName may not be present for e.g. anonymous functions
const NSRange methodNameRange = [match rangeAtIndex:1];
NSString *methodName = methodNameRange.location == NSNotFound ? nil : [line substringWithRange:methodNameRange];
NSString *file = [line substringWithRange:[match rangeAtIndex:2]];
NSString *lineNumber = [line substringWithRange:[match rangeAtIndex:3]];
NSString *column = [line substringWithRange:[match rangeAtIndex:4]];
Expand All @@ -69,7 +71,7 @@ + (instancetype)stackFrameWithLine:(NSString *)line

+ (instancetype)stackFrameWithDictionary:(NSDictionary *)dict
{
return [[self alloc] initWithMethodName:dict[@"methodName"]
return [[self alloc] initWithMethodName:RCTNilIfNull(dict[@"methodName"])
file:dict[@"file"]
lineNumber:[RCTNilIfNull(dict[@"lineNumber"]) integerValue]
column:[RCTNilIfNull(dict[@"column"]) integerValue]];
Expand Down
3 changes: 3 additions & 0 deletions React/Base/RCTReloadCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@

/** Registers a weakly-held observer of the Command+R reload key command. */
RCT_EXTERN void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener);

/** Triggers a reload for all current listeners. You shouldn't need to use this directly in most cases. */
RCT_EXTERN void RCTTriggerReloadCommandListeners(void);
26 changes: 16 additions & 10 deletions React/Base/RCTReloadCommand.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,35 @@

#import "RCTReloadCommand.h"

#import "RCTAssert.h"
#import "RCTKeyCommands.h"

/** main queue only */
static NSHashTable<id<RCTReloadListener>> *listeners;

void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
{
static NSHashTable<id<RCTReloadListener>> *listeners;
RCTAssertMainQueue(); // because registerKeyCommandWithInput: must be called on the main thread
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
listeners = [NSHashTable weakObjectsHashTable];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:
^(__unused UIKeyCommand *command) {
NSArray<id<RCTReloadListener>> *copiedListeners;
@synchronized (listeners) { // avoid mutation-while-enumerating
copiedListeners = [listeners allObjects];
}
for (id<RCTReloadListener> l in copiedListeners) {
[l didReceiveReloadCommand];
}
RCTTriggerReloadCommandListeners();
}];
});
[listeners addObject:listener];
}

@synchronized (listeners) {
[listeners addObject:listener];
void RCTTriggerReloadCommandListeners(void)
{
RCTAssertMainQueue();
// Copy to protect against mutation-during-enumeration.
// If listeners hasn't been initialized yet we get nil, which works just fine.
NSArray<id<RCTReloadListener>> *copiedListeners = [listeners allObjects];
for (id<RCTReloadListener> l in copiedListeners) {
[l didReceiveReloadCommand];
}
}
10 changes: 10 additions & 0 deletions React/Modules/RCTRedBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTErrorCustomizer.h>

@class RCTJSStackFrame;

@interface RCTRedBox : NSObject <RCTBridgeModule>

- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)errorCustomizer;
Expand All @@ -22,9 +24,17 @@
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack;
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack;
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack;
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack;

- (void)dismiss;

/** Overrides bridge.bundleURL. Modify on main thread only. You shouldn't need to use this. */
@property (nonatomic, strong) NSURL *overrideBundleURL;

/** Overrides the default behavior of calling [bridge reload] on reload. You shouldn't need to use this. */
@property (nonatomic, strong) dispatch_block_t overrideReloadAction;

@end

/**
Expand Down
45 changes: 29 additions & 16 deletions React/Modules/RCTRedBox.m
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStack
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
}

cell.textLabel.text = stackFrame.methodName;
cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
if (stackFrame.file) {
cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
} else {
Expand Down Expand Up @@ -375,44 +375,50 @@ - (void)showError:(NSError *)error

- (void)showErrorMessage:(NSString *)message
{
[self showErrorMessage:message withStack:nil isUpdate:NO];
[self showErrorMessage:message withParsedStack:nil isUpdate:NO];
}

- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
{
[self showErrorMessage:message withDetails:details stack:nil];
}

- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<id> *)stack {
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray<RCTJSStackFrame *> *)stack {
NSString *combinedMessage = message;
if (details) {
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
}
[self showErrorMessage:combinedMessage withStack:stack isUpdate:NO];
[self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO];
}

- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
{
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
[self showErrorMessage:message withStack:stack isUpdate:NO];
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
}

- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
{
[self showErrorMessage:message withStack:stack isUpdate:NO];
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO];
}

- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
{
[self showErrorMessage:message withStack:stack isUpdate:YES];
[self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES];
}

- (void)showErrorMessage:(NSString *)message withStack:(NSArray<id> *)stack isUpdate:(BOOL)isUpdate
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
{
if (![[stack firstObject] isKindOfClass:[RCTJSStackFrame class]]) {
stack = [RCTJSStackFrame stackFramesWithDictionaries:stack];
}
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
}

- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
{
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
}

- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate
{
dispatch_async(dispatch_get_main_queue(), ^{
if (!self->_window) {
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
Expand Down Expand Up @@ -441,15 +447,16 @@ - (void)invalidate

- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame
{
if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL;
if (![bundleURL.scheme hasPrefix:@"http"]) {
RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager.");
return;
}

NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
NSMutableURLRequest *request = [NSMutableURLRequest new];
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:_bridge.bundleURL];
request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL];
request.HTTPMethod = @"POST";
request.HTTPBody = stackFrameJSON;
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
Expand All @@ -460,7 +467,11 @@ - (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEd

- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow
{
[_bridge reload];
if (_overrideReloadAction) {
_overrideReloadAction();
} else {
[_bridge reload];
}
[self dismiss];
}

Expand All @@ -487,6 +498,8 @@ - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details {}
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack {}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack {}
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack {}
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack {}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate {}
- (void)dismiss {}

Expand Down

0 comments on commit e87904c

Please sign in to comment.