Fix Flipper crashing to an empty screen
Summary: Changelog: Fixed issue where a Flipper crash would result in an entirely blank screen, rather than a useful error message. While debugging another issue, discovered that React errors that happen outside a Plugin aren't caught at all, resulting in the infamous gray screen of deaths. This was the case because no error boundary has been set up for our Chrome, and since React 16 the default error handling has becoming rendering blank, rather than freezing. See https://reactjs.org/docs/error-boundaries.html#new-behavior-for-uncaught-errors. Thanks to ant.design styling this decently was trivial :). But sadly involved a component class since error boundaries are not yet available as hook. With these changes the errors should also end up more readably in our monitoring. Reviewed By: nikoant Differential Revision: D26422666 fbshipit-source-id: 6c0f8611c80a4a5e0d7e61d58afcf5eabe410e57
This commit is contained in:
committed by
Facebook GitHub Bot
parent
11548c9cd6
commit
4964966b91
@@ -40,8 +40,15 @@ import {
|
|||||||
_setGlobalInteractionReporter,
|
_setGlobalInteractionReporter,
|
||||||
Logger,
|
Logger,
|
||||||
_LoggerContext,
|
_LoggerContext,
|
||||||
|
Layout,
|
||||||
|
theme,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import isProduction from './utils/isProduction';
|
import isProduction from './utils/isProduction';
|
||||||
|
import {Button, Input, Result, Typography} from 'antd';
|
||||||
|
import constants from './fb-stubs/constants';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import {CopyOutlined} from '@ant-design/icons';
|
||||||
|
import {clipboard} from 'electron/common';
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
|
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
|
||||||
// By default Node.JS has its internal certificate storage and doesn't use
|
// By default Node.JS has its internal certificate storage and doesn't use
|
||||||
@@ -58,7 +65,63 @@ enableMapSet();
|
|||||||
|
|
||||||
GK.init();
|
GK.init();
|
||||||
|
|
||||||
const AppFrame = ({logger}: {logger: Logger}) => (
|
class AppFrame extends React.Component<
|
||||||
|
{logger: Logger},
|
||||||
|
{error: any; errorInfo: any}
|
||||||
|
> {
|
||||||
|
state = {error: undefined as any, errorInfo: undefined as any};
|
||||||
|
|
||||||
|
getError() {
|
||||||
|
return this.state.error
|
||||||
|
? `${this.state.error}\n\nComponent stack:\n${this.state.errorInfo?.componentStack}\n\nError stacktrace:\n${this.state.error?.stack}`
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {logger} = this.props;
|
||||||
|
return this.state.error ? (
|
||||||
|
<Layout.Container grow center pad={80} style={{height: '100%'}}>
|
||||||
|
<Layout.Top style={{maxWidth: 800, height: '100%'}}>
|
||||||
|
<Result
|
||||||
|
status="error"
|
||||||
|
title="Detected a Flipper crash"
|
||||||
|
subTitle={
|
||||||
|
<p>
|
||||||
|
A crash was detected in the Flipper chrome. Filing a{' '}
|
||||||
|
<Typography.Link
|
||||||
|
href={
|
||||||
|
constants.IS_PUBLIC_BUILD
|
||||||
|
? 'https://github.com/facebook/flipper/issues/new/choose'
|
||||||
|
: constants.FEEDBACK_GROUP_LINK
|
||||||
|
}>
|
||||||
|
bug report
|
||||||
|
</Typography.Link>{' '}
|
||||||
|
would be appreciated! Please include the details below.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
extra={[
|
||||||
|
<Button
|
||||||
|
key="copy_error"
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
clipboard.writeText(this.getError());
|
||||||
|
}}>
|
||||||
|
Copy error
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="retry_error"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({error: undefined, errorInfo: undefined});
|
||||||
|
}}>
|
||||||
|
Retry
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<CodeBlock value={this.getError()} readOnly />
|
||||||
|
</Layout.Top>
|
||||||
|
</Layout.Container>
|
||||||
|
) : (
|
||||||
<_LoggerContext.Provider value={logger}>
|
<_LoggerContext.Provider value={logger}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<CacheProvider value={cache}>
|
<CacheProvider value={cache}>
|
||||||
@@ -74,7 +137,21 @@ const AppFrame = ({logger}: {logger: Logger}) => (
|
|||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
</_LoggerContext.Provider>
|
</_LoggerContext.Provider>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: any, errorInfo: any) {
|
||||||
|
console.error(
|
||||||
|
`Flipper chrome crash: ${error}`,
|
||||||
|
error,
|
||||||
|
'\nComponents: ' + errorInfo?.componentStack,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
error,
|
||||||
|
errorInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setProcessState(store: Store) {
|
function setProcessState(store: Store) {
|
||||||
const settings = store.getState().settingsState;
|
const settings = store.getState().settingsState;
|
||||||
@@ -146,3 +223,10 @@ const persistor = persistStore(store, undefined, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setPersistor(persistor);
|
setPersistor(persistor);
|
||||||
|
|
||||||
|
const CodeBlock = styled(Input.TextArea)({
|
||||||
|
fontFamily:
|
||||||
|
'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;',
|
||||||
|
fontSize: '0.8em',
|
||||||
|
color: theme.textColorSecondary,
|
||||||
|
});
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ type SplitLayoutProps = {
|
|||||||
*/
|
*/
|
||||||
center?: boolean;
|
center?: boolean;
|
||||||
children: [React.ReactNode, React.ReactNode];
|
children: [React.ReactNode, React.ReactNode];
|
||||||
|
style?: React.HTMLAttributes<HTMLDivElement>['style'];
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderSplitLayout(
|
function renderSplitLayout(
|
||||||
|
|||||||
Reference in New Issue
Block a user