Add NativeLogBox module on iOS

Summary:
This diff adds a NativeLogBox module implementation on iOS to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view.

The strategy here is:
- initialize: will create a hidden window (the way redbox does) and render the LogBox to it
- show: will show the window
- hide: will hide the window

Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the window with the `initialize` function so that it's warm by the time LogBox needs to render.

Changelog: [Internal]

Reviewed By: RSNara

Differential Revision: D18750008

fbshipit-source-id: 013e55ded55c8572bb08e0219ff4cd0060ebe0da
This commit is contained in:
Rick Hanlon
2019-12-10 19:52:44 +00:00
parent 041c392ec9
commit 45a43241cd
9 changed files with 286 additions and 0 deletions
@@ -1671,6 +1671,33 @@ namespace facebook {
}
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_show(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "show", @selector(show), args, count);
}
static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_hide(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "hide", @selector(hide), args, count);
}
NativeLogBoxSpecJSI::NativeLogBoxSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker)
: ObjCTurboModule("LogBox", instance, jsInvoker) {
methodMap_["show"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_show};
methodMap_["hide"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_hide};
}
} // namespace react
@@ -1771,6 +1771,25 @@ namespace facebook {
};
} // namespace react
} // namespace facebook
@protocol NativeLogBoxSpec <RCTBridgeModule, RCTTurboModule>
- (void)show;
- (void)hide;
@end
namespace facebook {
namespace react {
/**
* ObjC++ class for module 'LogBox'
*/
class JSI_EXPORT NativeLogBoxSpecJSI : public ObjCTurboModule {
public:
NativeLogBoxSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<CallInvoker> jsInvoker);
};
} // namespace react
} // namespace facebook
@protocol NativeModalManagerSpec <RCTBridgeModule, RCTTurboModule>
- (void)addListener:(NSString *)eventName;
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+show: () => void;
+hide: () => void;
}
export default (TurboModuleRegistry.get<Spec>('LogBox'): ?Spec);
+3
View File
@@ -96,6 +96,9 @@ rn_apple_library(
) + react_module_plugin_providers(
name = "RedBox",
native_class_func = "RCTRedBoxCls",
) + react_module_plugin_providers(
name = "LogBox",
native_class_func = "RCTLogBoxCls",
) + react_module_plugin_providers(
name = "TVNavigationEventEmitter",
native_class_func = "RCTTVNavigationEventEmitterCls",
+1
View File
@@ -48,6 +48,7 @@ Class RCTPerfMonitorCls(void) __attribute__((used));
Class RCTDevMenuCls(void) __attribute__((used));
Class RCTDevSettingsCls(void) __attribute__((used));
Class RCTRedBoxCls(void) __attribute__((used));
Class RCTLogBoxCls(void) __attribute__((used));
Class RCTTVNavigationEventEmitterCls(void) __attribute__((used));
Class RCTWebSocketExecutorCls(void) __attribute__((used));
Class RCTWebSocketModuleCls(void) __attribute__((used));
+1
View File
@@ -37,6 +37,7 @@ Class RCTCoreModulesClassProvider(const char *name) {
{"DevMenu", RCTDevMenuCls},
{"DevSettings", RCTDevSettingsCls},
{"RedBox", RCTRedBoxCls},
{"LogBox", RCTLogBoxCls},
{"TVNavigationEventEmitter", RCTTVNavigationEventEmitterCls},
{"WebSocketExecutor", RCTWebSocketExecutorCls},
{"WebSocketModule", RCTWebSocketModuleCls},
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTErrorCustomizer.h>
@class RCTJSStackFrame;
@interface RCTLogBox : NSObject <RCTBridgeModule>
- (void)show;
- (void)hide;
@end
+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTLogBox.h"
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTErrorInfo.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTJSStackFrame.h>
#import <React/RCTReloadCommand.h>
#import <React/RCTUtils.h>
#import <objc/runtime.h>
#import "CoreModulesPlugins.h"
#if RCT_DEV_MENU
@class RCTLogBoxWindow;
@protocol RCTLogBoxWindowActionDelegate <NSObject>
- (void)logBoxWindow:(RCTLogBoxWindow *)logBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame;
- (void)reloadFromlogBoxWindow:(RCTLogBoxWindow *)logBoxWindow;
- (void)loadExtraDataViewController;
@end
@interface RCTLogBoxWindow : UIWindow <UITableViewDelegate>
@property (nonatomic, weak) id<RCTLogBoxWindowActionDelegate> actionDelegate;
@end
@implementation RCTLogBoxWindow
{
UITableView *_stackTraceTableView;
NSString *_lastErrorMessage;
NSArray<RCTJSStackFrame *> *_lastStackTrace;
int _lastErrorCookie;
}
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge
{
_lastErrorCookie = -1;
if ((self = [super initWithFrame:frame])) {
#if TARGET_OS_TV
self.windowLevel = UIWindowLevelAlert + 1000;
#else
self.windowLevel = UIWindowLevelStatusBar - 1;
#endif
self.backgroundColor = [UIColor clearColor];
self.hidden = YES;
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:nil];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.rootViewController = rootViewController;
}
return self;
}
- (void)show
{
[self becomeFirstResponder];
[self makeKeyAndVisible];
}
- (void)dismiss
{
self.hidden = YES;
[self resignFirstResponder];
[RCTSharedApplication().delegate.window makeKeyWindow];
}
@end
@interface RCTLogBox () <NativeLogBoxSpec>
@end
@implementation RCTLogBox
{
RCTLogBoxWindow *_window;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
dispatch_async(dispatch_get_main_queue(), ^{
self->_window = [[RCTLogBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds bridge: self->_bridge];
});
}
RCT_EXPORT_METHOD(show)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self->_window show];
});
}
RCT_EXPORT_METHOD(hide)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self->_window dismiss];
});
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
}
@end
#else // Disabled
@interface RCTLogBox() <NativeLogBoxSpec>
@end
@implementation RCTLogBox
+ (NSString *)moduleName
{
return nil;
}
- (void)show {
// noop
}
- (void)hide {
// noop
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
}
@end
#endif
Class RCTLogBoxCls(void) {
return RCTLogBox.class;
}
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*
* <p>Generated by an internal genrule from Flow types.
*
* @generated
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactModuleWithSpec;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
public abstract class NativeLogBoxSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
public NativeLogBoxSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public abstract void hide();
@ReactMethod
public abstract void show();
}