mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
LogBox - lazily initialize on iOS, use sync APIs
Summary: Update LogBox on iOS to lazily initialize, using a synchronous RCTSurface, behind RCTSharedApplication checks. This results in faster of LogBox, without keeping around a long lived window in the background, and only used when LogBox is used. On Android, we still start the react app in the background but we create a dialog when it's shown and then destroy it when it's hidden. Once we have the sync APIs on android we can update it to use the same strategy. Changelog: [Internal] Reviewed By: fkgozali Differential Revision: D18925538 fbshipit-source-id: 1a72c39aa0fc26c8ba657d36c7fa7bc0ae777eb9
This commit is contained in:
@@ -22,7 +22,7 @@ import type {
|
||||
} from './parseLogBoxLog';
|
||||
import parseErrorStack from '../../Core/Devtools/parseErrorStack';
|
||||
import type {ExtendedError} from '../../Core/Devtools/parseErrorStack';
|
||||
|
||||
import NativeLogBox from '../../NativeModules/specs/NativeLogBox';
|
||||
export type LogBoxLogs = Set<LogBoxLog>;
|
||||
export type LogData = $ReadOnly<{|
|
||||
level: LogLevel,
|
||||
@@ -86,20 +86,6 @@ const LOGBOX_ERROR_MESSAGE =
|
||||
'An error was thrown when attempting to render log messages via LogBox.';
|
||||
|
||||
function getNextState() {
|
||||
const logArray = Array.from(logs);
|
||||
let index = logArray.length - 1;
|
||||
while (index >= 0) {
|
||||
// The latest syntax error is selected and displayed before all other logs.
|
||||
if (logArray[index].level === 'syntax') {
|
||||
return {
|
||||
logs,
|
||||
isDisabled: _isDisabled,
|
||||
selectedLogIndex: index,
|
||||
};
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
|
||||
return {
|
||||
logs,
|
||||
isDisabled: _isDisabled,
|
||||
@@ -175,9 +161,10 @@ function appendNewLog(newLog) {
|
||||
let addPendingLog = () => {
|
||||
logs.add(newLog);
|
||||
if (_selectedIndex <= 0) {
|
||||
_selectedIndex = logs.size - 1;
|
||||
setSelectedLog(logs.size - 1);
|
||||
} else {
|
||||
handleUpdate();
|
||||
}
|
||||
handleUpdate();
|
||||
addPendingLog = null;
|
||||
};
|
||||
|
||||
@@ -196,6 +183,9 @@ function appendNewLog(newLog) {
|
||||
handleUpdate();
|
||||
}
|
||||
});
|
||||
} else if (newLog.level === 'syntax') {
|
||||
logs.add(newLog);
|
||||
setSelectedLog(logs.size - 1);
|
||||
} else {
|
||||
logs.add(newLog);
|
||||
handleUpdate();
|
||||
@@ -259,21 +249,42 @@ export function symbolicateLogLazy(log: LogBoxLog) {
|
||||
export function clear(): void {
|
||||
if (logs.size > 0) {
|
||||
logs = new Set();
|
||||
_selectedIndex = -1;
|
||||
handleUpdate();
|
||||
setSelectedLog(-1);
|
||||
}
|
||||
}
|
||||
|
||||
export function setSelectedLog(index: number): void {
|
||||
_selectedIndex = index;
|
||||
export function setSelectedLog(proposedNewIndex: number): void {
|
||||
const oldIndex = _selectedIndex;
|
||||
let newIndex = proposedNewIndex;
|
||||
|
||||
const logArray = Array.from(logs);
|
||||
let index = logArray.length - 1;
|
||||
while (index >= 0) {
|
||||
// The latest syntax error is selected and displayed before all other logs.
|
||||
if (logArray[index].level === 'syntax') {
|
||||
newIndex = index;
|
||||
break;
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
_selectedIndex = newIndex;
|
||||
handleUpdate();
|
||||
if (NativeLogBox) {
|
||||
setTimeout(() => {
|
||||
if (oldIndex < 0 && newIndex >= 0) {
|
||||
NativeLogBox.show();
|
||||
} else if (oldIndex >= 0 && newIndex < 0) {
|
||||
NativeLogBox.hide();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearWarnings(): void {
|
||||
const newLogs = Array.from(logs).filter(log => log.level !== 'warn');
|
||||
if (newLogs.length !== logs.size) {
|
||||
logs = new Set(newLogs);
|
||||
_selectedIndex = -1;
|
||||
setSelectedLog(-1);
|
||||
handleUpdate();
|
||||
}
|
||||
}
|
||||
@@ -284,8 +295,7 @@ export function clearErrors(): void {
|
||||
);
|
||||
if (newLogs.length !== logs.size) {
|
||||
logs = new Set(newLogs);
|
||||
_selectedIndex = -1;
|
||||
handleUpdate();
|
||||
setSelectedLog(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {View, StyleSheet} from 'react-native';
|
||||
import * as LogBoxData from './Data/LogBoxData';
|
||||
import LogBoxInspector from './UI/LogBoxInspector';
|
||||
import type LogBoxLog from './Data/LogBoxLog';
|
||||
import NativeLogBox from '../NativeModules/specs/NativeLogBox';
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
logs: $ReadOnlyArray<LogBoxLog>,
|
||||
@@ -23,35 +22,18 @@ type Props = $ReadOnly<{|
|
||||
isDisabled?: ?boolean,
|
||||
|}>;
|
||||
|
||||
function NativeLogBoxVisibility(props) {
|
||||
React.useLayoutEffect(() => {
|
||||
if (NativeLogBox) {
|
||||
if (props.visible) {
|
||||
// Schedule this to try and prevent flashing the old state.
|
||||
setTimeout(() => NativeLogBox.show(), 10);
|
||||
} else {
|
||||
NativeLogBox.hide();
|
||||
}
|
||||
}
|
||||
}, [props.visible]);
|
||||
|
||||
return props.children;
|
||||
}
|
||||
|
||||
export class _LogBoxInspectorContainer extends React.Component<Props> {
|
||||
render(): React.Node {
|
||||
return (
|
||||
<NativeLogBoxVisibility visible={this.props.selectedLogIndex >= 0}>
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
<LogBoxInspector
|
||||
onDismiss={this._handleDismiss}
|
||||
onMinimize={this._handleMinimize}
|
||||
onChangeSelectedIndex={this._handleSetSelectedLog}
|
||||
logs={this.props.logs}
|
||||
selectedIndex={this.props.selectedLogIndex}
|
||||
/>
|
||||
</View>
|
||||
</NativeLogBoxVisibility>
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
<LogBoxInspector
|
||||
onDismiss={this._handleDismiss}
|
||||
onMinimize={this._handleMinimize}
|
||||
onChangeSelectedIndex={this._handleSetSelectedLog}
|
||||
logs={this.props.logs}
|
||||
selectedIndex={this.props.selectedLogIndex}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ type Props = $ReadOnly<{|
|
||||
|
||||
function LogBoxInspector(props: Props): React.Node {
|
||||
const {logs, selectedIndex} = props;
|
||||
let log = logs[selectedIndex];
|
||||
|
||||
const log = logs[selectedIndex];
|
||||
React.useEffect(() => {
|
||||
if (log) {
|
||||
LogBoxData.symbolicateLogNow(log);
|
||||
|
||||
@@ -101,9 +101,11 @@ const styles = StyleSheet.create({
|
||||
syntaxErrorText: {
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
height: 48,
|
||||
fontSize: 14,
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
lineHeight: 20,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 50,
|
||||
fontStyle: 'italic',
|
||||
color: LogBoxStyle.getTextColor(0.6),
|
||||
},
|
||||
|
||||
@@ -29,8 +29,10 @@ exports[`LogBoxInspectorFooter should render no buttons and a message for syntax
|
||||
"color": "rgba(255, 255, 255, 0.6)",
|
||||
"fontSize": 14,
|
||||
"fontStyle": "italic",
|
||||
"paddingBottom": 15,
|
||||
"paddingTop": 15,
|
||||
"height": 48,
|
||||
"lineHeight": 20,
|
||||
"paddingBottom": 50,
|
||||
"paddingTop": 20,
|
||||
"textAlign": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
|
||||
@@ -1,66 +1,62 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LogBoxNotificationContainer should render inspector with logs, even when disabled 1`] = `
|
||||
<NativeLogBoxVisibility
|
||||
visible={false}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
>
|
||||
<LogBoxInspector
|
||||
logs={
|
||||
Array [
|
||||
LogBoxLog {
|
||||
"category": "Some kind of message",
|
||||
"codeFrame": undefined,
|
||||
"componentStack": Array [],
|
||||
"count": 1,
|
||||
"isComponentError": false,
|
||||
"level": "warn",
|
||||
"message": Object {
|
||||
"content": "Some kind of message",
|
||||
"substitutions": Array [],
|
||||
},
|
||||
"stack": Array [],
|
||||
"symbolicated": Object {
|
||||
"error": null,
|
||||
"stack": null,
|
||||
"status": "NONE",
|
||||
},
|
||||
}
|
||||
>
|
||||
<LogBoxInspector
|
||||
logs={
|
||||
Array [
|
||||
LogBoxLog {
|
||||
"category": "Some kind of message",
|
||||
"codeFrame": undefined,
|
||||
"componentStack": Array [],
|
||||
"count": 1,
|
||||
"isComponentError": false,
|
||||
"level": "warn",
|
||||
"message": Object {
|
||||
"content": "Some kind of message",
|
||||
"substitutions": Array [],
|
||||
},
|
||||
LogBoxLog {
|
||||
"category": "Some kind of message (latest)",
|
||||
"codeFrame": undefined,
|
||||
"componentStack": Array [],
|
||||
"count": 1,
|
||||
"isComponentError": false,
|
||||
"level": "error",
|
||||
"message": Object {
|
||||
"content": "Some kind of message (latest)",
|
||||
"substitutions": Array [],
|
||||
},
|
||||
"stack": Array [],
|
||||
"symbolicated": Object {
|
||||
"error": null,
|
||||
"stack": null,
|
||||
"status": "NONE",
|
||||
},
|
||||
"stack": Array [],
|
||||
"symbolicated": Object {
|
||||
"error": null,
|
||||
"stack": null,
|
||||
"status": "NONE",
|
||||
},
|
||||
]
|
||||
}
|
||||
onChangeSelectedIndex={[Function]}
|
||||
onDismiss={[Function]}
|
||||
onMinimize={[Function]}
|
||||
selectedIndex={-1}
|
||||
/>
|
||||
</View>
|
||||
</NativeLogBoxVisibility>
|
||||
},
|
||||
LogBoxLog {
|
||||
"category": "Some kind of message (latest)",
|
||||
"codeFrame": undefined,
|
||||
"componentStack": Array [],
|
||||
"count": 1,
|
||||
"isComponentError": false,
|
||||
"level": "error",
|
||||
"message": Object {
|
||||
"content": "Some kind of message (latest)",
|
||||
"substitutions": Array [],
|
||||
},
|
||||
"stack": Array [],
|
||||
"symbolicated": Object {
|
||||
"error": null,
|
||||
"stack": null,
|
||||
"status": "NONE",
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
onChangeSelectedIndex={[Function]}
|
||||
onDismiss={[Function]}
|
||||
onMinimize={[Function]}
|
||||
selectedIndex={-1}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#import <React/RCTJSStackFrame.h>
|
||||
#import <React/RCTRedBoxSetEnabled.h>
|
||||
#import <React/RCTReloadCommand.h>
|
||||
#import <React/RCTRedBoxSetEnabled.h>
|
||||
#import <React/RCTSurface.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
@@ -25,61 +27,52 @@
|
||||
|
||||
#if RCT_DEV_MENU
|
||||
|
||||
@class RCTLogBoxWindow;
|
||||
|
||||
@protocol RCTLogBoxWindowActionDelegate <NSObject>
|
||||
|
||||
- (void)logBoxWindow:(RCTLogBoxWindow *)logBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame;
|
||||
- (void)reloadFromlogBoxWindow:(RCTLogBoxWindow *)logBoxWindow;
|
||||
- (void)loadExtraDataViewController;
|
||||
@class RCTLogBoxView;
|
||||
|
||||
@interface RCTLogBoxView : UIView
|
||||
@end
|
||||
|
||||
@interface RCTLogBoxWindow : UIWindow <UITableViewDelegate>
|
||||
@property (nonatomic, weak) id<RCTLogBoxWindowActionDelegate> actionDelegate;
|
||||
@end
|
||||
|
||||
@implementation RCTLogBoxWindow
|
||||
@implementation RCTLogBoxView
|
||||
{
|
||||
UITableView *_stackTraceTableView;
|
||||
NSString *_lastErrorMessage;
|
||||
NSArray<RCTJSStackFrame *> *_lastStackTrace;
|
||||
int _lastErrorCookie;
|
||||
UIViewController *_rootViewController;
|
||||
RCTSurface *_surface;
|
||||
}
|
||||
|
||||
- (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];
|
||||
_surface = [[RCTSurface alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:@{}];
|
||||
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.rootViewController = rootViewController;
|
||||
[_surface start];
|
||||
[_surface setSize:frame.size];
|
||||
|
||||
if (![_surface synchronouslyWaitForStage:RCTSurfaceStageSurfaceDidInitialMounting timeout:.5]) {
|
||||
RCTLogInfo(@"Failed to mount LogBox within 500ms");
|
||||
}
|
||||
|
||||
_rootViewController = [UIViewController new];
|
||||
_rootViewController.view = (UIView *)_surface.view;
|
||||
_rootViewController.view.backgroundColor = [UIColor clearColor];
|
||||
_rootViewController.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Dismiss by deallocating the window.
|
||||
// This will also handle JS reload, otherwise the LogBox view would be stuck on top.
|
||||
[_rootViewController.view resignFirstResponder];
|
||||
[_rootViewController dismissViewControllerAnimated:NO completion:NULL];
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
[self becomeFirstResponder];
|
||||
[self makeKeyAndVisible];
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
self.hidden = YES;
|
||||
[self resignFirstResponder];
|
||||
[RCTSharedApplication().delegate.window makeKeyWindow];
|
||||
[RCTSharedApplication().delegate.window.rootViewController presentViewController:_rootViewController animated:NO completion:^{
|
||||
[self->_rootViewController.view becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -89,7 +82,7 @@
|
||||
|
||||
@implementation RCTLogBox
|
||||
{
|
||||
RCTLogBoxWindow *_window;
|
||||
RCTLogBoxView *_view;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@@ -101,34 +94,34 @@ RCT_EXPORT_MODULE()
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
RCT_EXPORT_METHOD(show)
|
||||
{
|
||||
_bridge = bridge;
|
||||
|
||||
if (RCTRedBoxGetEnabled()) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_window = [[RCTLogBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds bridge: self->_bridge];
|
||||
if (!self->_view) {
|
||||
self->_view = [[RCTLogBoxView alloc] initWithFrame:[UIScreen mainScreen].bounds bridge: self->_bridge];
|
||||
}
|
||||
[self->_view show];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
});
|
||||
if (RCTRedBoxGetEnabled()) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_view = nil;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
||||
{
|
||||
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
|
||||
if (RCTRedBoxGetEnabled()) {
|
||||
return std::make_shared<facebook::react::NativeLogBoxSpecJSI>(self, jsInvoker);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user