mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
Improved logging and dev menu
This commit is contained in:
parent
2186691812
commit
0b21df4a34
@ -264,7 +264,9 @@ RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag)
|
||||
RCTAnimationExperimentalManager *strongSelf = weakSelf;
|
||||
|
||||
NSNumber *reactTag = strongSelf->_animationRegistry[animationTag];
|
||||
if (!reactTag) return;
|
||||
if (!reactTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
for (NSString *animationKey in view.layer.animationKeys) {
|
||||
|
@ -9,11 +9,50 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTBridge;
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@interface RCTDevMenu : NSObject
|
||||
/**
|
||||
* Developer menu, useful for exposing extra functionality when debugging.
|
||||
*/
|
||||
@interface RCTDevMenu : NSObject <RCTBridgeModule, RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
/**
|
||||
* Is the menu enabled. The menu is enabled by default in debug mode, but
|
||||
* you may wish to disable it so that you can provide your own shake handler.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shakeToShow;
|
||||
|
||||
/**
|
||||
* Enables performance profiling.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL profilingEnabled;
|
||||
|
||||
/**
|
||||
* Enables automatic polling for JS code changes. Only applicable when
|
||||
* running the app from a server.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL liveReloadEnabled;
|
||||
|
||||
/**
|
||||
* The time between checks for code changes. Defaults to 1 second.
|
||||
*/
|
||||
@property (nonatomic, assign) NSTimeInterval liveReloadPeriod;
|
||||
|
||||
/**
|
||||
* Manually show the menu. This will.
|
||||
*/
|
||||
- (void)show;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This category makes the developer menu instance available via the
|
||||
* RCTBridge, which is useful for any class that needs to access the menu.
|
||||
*/
|
||||
@interface RCTBridge (RCTDevMenu)
|
||||
|
||||
@property (nonatomic, readonly) RCTDevMenu *devMenu;
|
||||
|
||||
@end
|
||||
|
@ -9,12 +9,13 @@
|
||||
|
||||
#import "RCTDevMenu.h"
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTWebViewExecutor.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTBridge (RCTDevMenu)
|
||||
@interface RCTBridge (Profiling)
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||
|
||||
@ -23,87 +24,206 @@
|
||||
|
||||
@end
|
||||
|
||||
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
|
||||
|
||||
@implementation UIWindow (RCTDevMenu)
|
||||
|
||||
- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
||||
{
|
||||
if (event.subtype == UIEventSubtypeMotionShake) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTDevMenu () <UIActionSheetDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTDevMenu
|
||||
{
|
||||
BOOL _liveReload;
|
||||
__weak RCTBridge *_bridge;
|
||||
NSTimer *_updateTimer;
|
||||
UIActionSheet *_actionSheet;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
// We're swizzling here because it's poor form to override methods in a category,
|
||||
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
|
||||
// no need to call the original implementation.
|
||||
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_shakeToShow = YES;
|
||||
_liveReloadPeriod = 1.0; // 1 second
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(showOnShake)
|
||||
name:RCTShowDevMenuNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)showOnShake
|
||||
{
|
||||
if (_shakeToShow) {
|
||||
[self show];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
||||
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
||||
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
if (_actionSheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
||||
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
||||
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling";
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
||||
|
||||
UIActionSheet *actionSheet =
|
||||
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
||||
|
||||
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
||||
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
|
||||
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
|
||||
}
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
if (buttonIndex == 0) {
|
||||
[_bridge reload];
|
||||
} else if (buttonIndex == 1) {
|
||||
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
|
||||
[_bridge reload];
|
||||
} else if (buttonIndex == 2) {
|
||||
Class cls = [RCTWebViewExecutor class];
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
|
||||
[_bridge reload];
|
||||
} else if (buttonIndex == 3) {
|
||||
_liveReload = !_liveReload;
|
||||
[self _pollAndReload];
|
||||
} else if (buttonIndex == 4) {
|
||||
if (_bridge.profile) {
|
||||
[_bridge stopProfiling];
|
||||
} else {
|
||||
[_bridge startProfiling];
|
||||
}
|
||||
}
|
||||
}
|
||||
_actionSheet = nil;
|
||||
|
||||
- (void)_pollAndReload
|
||||
{
|
||||
if (_liveReload) {
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
NSURL *url = sourceCodeModule.scriptURL;
|
||||
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
|
||||
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_checkForUpdates:(NSURL *)URL
|
||||
{
|
||||
NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||
longPollRequest.timeoutInterval = 30;
|
||||
NSHTTPURLResponse *response;
|
||||
[NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_liveReload && response.statusCode == 205) {
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
switch (buttonIndex) {
|
||||
case 0: {
|
||||
[_bridge reload];
|
||||
break;
|
||||
}
|
||||
[self _pollAndReload];
|
||||
});
|
||||
case 1: {
|
||||
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
|
||||
[_bridge reload];
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
Class cls = NSClassFromString(@"RCTWebViewExecutor");
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
|
||||
[_bridge reload];
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
self.liveReloadEnabled = !_liveReloadEnabled;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
self.profilingEnabled = !_profilingEnabled;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setProfilingEnabled:(BOOL)enabled
|
||||
{
|
||||
if (_profilingEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_profilingEnabled = enabled;
|
||||
if (_bridge.profile) {
|
||||
[_bridge stopProfiling];
|
||||
} else {
|
||||
[_bridge startProfiling];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLiveReloadEnabled:(BOOL)enabled
|
||||
{
|
||||
if (_liveReloadEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_liveReloadEnabled = enabled;
|
||||
if (_liveReloadEnabled) {
|
||||
|
||||
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
|
||||
target:self
|
||||
selector:@selector(pollForUpdates)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
} else {
|
||||
|
||||
[_updateTimer invalidate];
|
||||
_updateTimer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
|
||||
{
|
||||
_liveReloadPeriod = liveReloadPeriod;
|
||||
if (_liveReloadEnabled) {
|
||||
self.liveReloadEnabled = NO;
|
||||
self.liveReloadEnabled = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pollForUpdates
|
||||
{
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
if (!sourceCodeModule) {
|
||||
RCTLogError(@"RCTSourceCode module not found");
|
||||
self.liveReloadEnabled = NO;
|
||||
}
|
||||
|
||||
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
|
||||
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
|
||||
queue:[[NSOperationQueue alloc] init]
|
||||
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
|
||||
|
||||
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
|
||||
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
|
||||
[_bridge reload];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return !_liveReloadEnabled || _updateTimer != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
|
||||
[_updateTimer invalidate];
|
||||
_updateTimer = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTDevMenu)
|
||||
|
||||
- (RCTDevMenu *)devMenu
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1,4 +1,11 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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>
|
||||
|
||||
|
@ -1,4 +1,11 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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 "RCTJavaScriptLoader.h"
|
||||
|
||||
|
@ -31,8 +31,8 @@ const char *RCTLogLevels[] = {
|
||||
static RCTLogFunction RCTCurrentLogFunction;
|
||||
static RCTLogLevel RCTCurrentLogThreshold;
|
||||
|
||||
void RCTLogSetup(void) __attribute__((constructor));
|
||||
void RCTLogSetup()
|
||||
__attribute__((constructor))
|
||||
static void RCTLogSetup()
|
||||
{
|
||||
RCTCurrentLogFunction = RCTDefaultLogFunction;
|
||||
|
||||
|
@ -57,12 +57,6 @@
|
||||
*/
|
||||
@property (nonatomic, strong) Class executorClass;
|
||||
|
||||
/**
|
||||
* If YES will watch for shake gestures and show development menu
|
||||
* with options like "Reload", "Enable Debugging", etc.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableDevMenu;
|
||||
|
||||
/**
|
||||
* The backing view controller of the root view.
|
||||
*/
|
||||
|
@ -13,7 +13,6 @@
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTDevMenu.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
@ -42,7 +41,6 @@
|
||||
|
||||
@implementation RCTRootView
|
||||
{
|
||||
RCTDevMenu *_devMenu;
|
||||
RCTBridge *_bridge;
|
||||
RCTTouchHandler *_touchHandler;
|
||||
NSString *_moduleName;
|
||||
@ -60,12 +58,6 @@
|
||||
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
_enableDevMenu = YES;
|
||||
|
||||
#endif
|
||||
|
||||
_bridge = bridge;
|
||||
_moduleName = moduleName;
|
||||
|
||||
@ -120,18 +112,6 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
||||
{
|
||||
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
|
||||
if (!_devMenu) {
|
||||
_devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge];
|
||||
}
|
||||
[_devMenu show];
|
||||
} else {
|
||||
[super motionEnded:motion withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_IMPORT_METHOD(AppRegistry, runApplication)
|
||||
RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
||||
|
||||
|
@ -85,7 +85,14 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object,
|
||||
range:(NSRange){0, message.length}
|
||||
withTemplate:@"[$4$5] \t$2"];
|
||||
|
||||
_RCTLogFormat(RCTLogLevelInfo, NULL, -1, @"%@", message);
|
||||
// TODO: it would be good if log level was sent as a param, instead of this hack
|
||||
RCTLogLevel level = RCTLogLevelInfo;
|
||||
if ([message rangeOfString:@"error" options:NSCaseInsensitiveSearch].length) {
|
||||
level = RCTLogLevelError;
|
||||
} else if ([message rangeOfString:@"warning" options:NSCaseInsensitiveSearch].length) {
|
||||
level = RCTLogLevelWarning;
|
||||
}
|
||||
_RCTLogFormat(level, NULL, -1, @"%@", message);
|
||||
}
|
||||
|
||||
return JSValueMakeUndefined(context);
|
||||
@ -126,8 +133,6 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
|
||||
+ (void)runRunLoopThread
|
||||
{
|
||||
// TODO (#5906496): Investigate exactly what this does and why
|
||||
|
||||
@autoreleasepool {
|
||||
// copy thread name to pthread name
|
||||
pthread_setname_np([[[NSThread currentThread] name] UTF8String]);
|
||||
@ -273,11 +278,11 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
}
|
||||
|
||||
- (void)executeApplicationScript:(NSString *)script
|
||||
sourceURL:(NSURL *)url
|
||||
sourceURL:(NSURL *)sourceURL
|
||||
onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(url != nil, @"url should not be nil");
|
||||
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
|
||||
RCTAssert(sourceURL != nil, @"url should not be nil");
|
||||
|
||||
__weak RCTContextExecutor *weakSelf = self;
|
||||
[self executeBlockOnJavaScriptQueue:^{
|
||||
RCTContextExecutor *strongSelf = weakSelf;
|
||||
@ -286,17 +291,18 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
}
|
||||
JSValueRef jsError = NULL;
|
||||
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
|
||||
JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString);
|
||||
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, sourceURL, 0, &jsError);
|
||||
JSStringRelease(sourceURL);
|
||||
JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString);
|
||||
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError);
|
||||
JSStringRelease(jsURL);
|
||||
JSStringRelease(execJSString);
|
||||
|
||||
NSError *error;
|
||||
if (!result) {
|
||||
error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError);
|
||||
if (onComplete) {
|
||||
NSError *error;
|
||||
if (!result) {
|
||||
error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError);
|
||||
}
|
||||
onComplete(error);
|
||||
}
|
||||
|
||||
onComplete(error);
|
||||
}];
|
||||
}
|
||||
|
||||
@ -314,7 +320,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
asGlobalObjectNamed:(NSString *)objectName
|
||||
callback:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
|
||||
|
||||
#if DEBUG
|
||||
RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
|
||||
#endif
|
||||
@ -333,19 +339,21 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
NSString *errorDesc = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script];
|
||||
RCTLogError(@"%@", errorDesc);
|
||||
|
||||
NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}];
|
||||
onComplete(error);
|
||||
if (onComplete) {
|
||||
NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}];
|
||||
onComplete(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx);
|
||||
|
||||
JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName);
|
||||
JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
|
||||
JSStringRelease(JSName);
|
||||
onComplete(nil);
|
||||
if (onComplete) {
|
||||
onComplete(nil);
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -19,10 +19,6 @@
|
||||
NSUInteger _reloadRetries;
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
static NSUInteger RCTReloadRetries = 0;
|
||||
#endif
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate
|
||||
@ -47,27 +43,32 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
#if DEBUG
|
||||
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||
|
||||
#else
|
||||
if (RCTReloadRetries < _maxReloadAttempts) {
|
||||
RCTReloadRetries++;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
|
||||
|
||||
static NSUInteger reloadRetries = 0;
|
||||
const NSUInteger maxMessageLength = 75;
|
||||
|
||||
if (reloadRetries < _maxReloadAttempts) {
|
||||
|
||||
reloadRetries++;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
|
||||
object:nil];
|
||||
|
||||
} else {
|
||||
NSError *error;
|
||||
const NSUInteger MAX_SANITIZED_LENGTH = 75;
|
||||
|
||||
// Filter out numbers so the same base errors are mapped to the same categories independent of incorrect values.
|
||||
NSString *pattern = @"[+-]?\\d+[,.]?\\d*";
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
|
||||
RCTAssert(error == nil, @"Bad regex pattern: %@", pattern);
|
||||
NSString *sanitizedMessage = [regex stringByReplacingMatchesInString:message
|
||||
options:0
|
||||
range:NSMakeRange(0, message.length)
|
||||
withTemplate:@"<num>"];
|
||||
if (sanitizedMessage.length > MAX_SANITIZED_LENGTH) {
|
||||
sanitizedMessage = [[sanitizedMessage substringToIndex:MAX_SANITIZED_LENGTH] stringByAppendingString:@"..."];
|
||||
NSString *sanitizedMessage = [message stringByReplacingOccurrencesOfString:pattern withString:@"<num>" options:NSRegularExpressionSearch range:(NSRange){0, message.length}];
|
||||
|
||||
if (sanitizedMessage.length > maxMessageLength) {
|
||||
sanitizedMessage = [[sanitizedMessage substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
|
||||
}
|
||||
NSMutableString *prettyStack = [@"\n" mutableCopy];
|
||||
|
||||
NSMutableString *prettyStack = [NSMutableString stringWithString:@"\n"];
|
||||
for (NSDictionary *frame in stack) {
|
||||
[prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]];
|
||||
}
|
||||
@ -75,13 +76,21 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
|
||||
NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage];
|
||||
[NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message
|
||||
stack:(NSArray *)stack)
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
[[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack];
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -999,7 +999,7 @@ RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag
|
||||
* Only layouts for views that are within the rect passed in are returned. Invokes the error callback if the
|
||||
* passed in parent view does not exist. Invokes the supplied callback with the array of computed layouts.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect
|
||||
RCT_EXPORT_METHOD(measureViewsInRect:(CGRect)rect
|
||||
parentView:(NSNumber *)reactTag
|
||||
errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
@ -1011,7 +1011,7 @@ RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect
|
||||
}
|
||||
NSArray *childShadowViews = [shadowView reactSubviews];
|
||||
NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[childShadowViews count]];
|
||||
CGRect layoutRect = [RCTConvert CGRect:rect];
|
||||
|
||||
|
||||
[childShadowViews enumerateObjectsUsingBlock:^(RCTShadowView *childShadowView, NSUInteger idx, BOOL *stop) {
|
||||
CGRect childLayout = [childShadowView measureLayoutRelativeToAncestor:shadowView];
|
||||
@ -1026,10 +1026,11 @@ RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect
|
||||
CGFloat width = childLayout.size.width;
|
||||
CGFloat height = childLayout.size.height;
|
||||
|
||||
if (leftOffset <= layoutRect.origin.x + layoutRect.size.width &&
|
||||
leftOffset + width >= layoutRect.origin.x &&
|
||||
topOffset <= layoutRect.origin.y + layoutRect.size.height &&
|
||||
topOffset + height >= layoutRect.origin.y) {
|
||||
if (leftOffset <= rect.origin.x + rect.size.width &&
|
||||
leftOffset + width >= rect.origin.x &&
|
||||
topOffset <= rect.origin.y + rect.size.height &&
|
||||
topOffset + height >= rect.origin.y) {
|
||||
|
||||
// This view is within the layout rect
|
||||
NSDictionary *result = @{@"index": @(idx),
|
||||
@"left": @(leftOffset),
|
||||
|
Loading…
x
Reference in New Issue
Block a user