Skip to content

Commit cb6fd8c

Browse files
committed
Use a custom error object in place of cross-origin errors
Cross-origin errors aren't accessible by React in DEV mode because we catch errors using a global error handler, in order to preserve the "Pause on exceptions" behavior of the DevTools. When this happens, we should use a custom error object that explains what happened. For uncaught errors, the actual error message is logged to the console by the browser, so React should skip logging the message again.
1 parent 01e2470 commit cb6fd8c

File tree

4 files changed

+57
-20
lines changed

4 files changed

+57
-20
lines changed

fixtures/dom/src/components/fixtures/error-handling/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ class ErrorBoundary extends React.Component {
2626
render() {
2727
if (this.state.didThrow) {
2828
if (this.state.error) {
29-
return `Captured an error: ${this.state.error.message}`;
29+
return <p>Captured an error: {this.state.error.message}</p>;
3030
} else {
31-
return `Captured an error: ${this.state.error}`;
31+
return <p>Captured an error: {'' + this.state.error}</p>;
3232
}
3333
}
3434
if (this.state.shouldThrow) {
@@ -45,12 +45,12 @@ class Example extends React.Component {
4545
render() {
4646
return (
4747
<div>
48+
<button onClick={this.restart}>Reset</button>
4849
<ErrorBoundary
4950
buttonText={this.props.buttonText}
5051
throws={this.props.throws}
5152
key={this.state.key}
5253
/>
53-
<button onClick={this.restart}>Reset</button>
5454
</div>
5555
);
5656
}
@@ -93,7 +93,7 @@ export default class ErrorHandlingTestCases extends React.Component {
9393
</TestCase.ExpectedResult>
9494
<Example
9595
throws={() => {
96-
throw null;
96+
throw null; // eslint-disable-line no-throw-literal
9797
}}
9898
/>
9999
</TestCase>
@@ -106,7 +106,7 @@ export default class ErrorHandlingTestCases extends React.Component {
106106
</TestCase.Steps>
107107
<TestCase.ExpectedResult>
108108
The "Trigger error" button should be replaced with "Captured an
109-
error: (TODO: custom error text)". The actual error message should
109+
error: A cross-origin error was thrown [...]". The actual error message should
110110
be logged to the console: "Uncaught Error: Expected true to
111111
be false".
112112
</TestCase.ExpectedResult>

src/renderers/shared/fiber/ReactFiberErrorLogger.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ function logCapturedError(capturedError: CapturedError): void {
5959
errorBoundaryName,
6060
errorBoundaryFound,
6161
willRetry,
62+
shouldIgnoreErrorMessage,
6263
} = capturedError;
6364

6465
const errorSummary = message ? `${name}: ${message}` : name;
@@ -99,12 +100,15 @@ function logCapturedError(capturedError: CapturedError): void {
99100
'See https://fb.me/react-error-boundaries for more information.';
100101
}
101102

102-
console.error(
103-
`${componentNameMessage} You should fix this error in your code. ${errorBoundaryMessage}\n\n` +
104-
`${errorSummary}\n\n` +
105-
`The error is located at: ${componentStack}\n\n` +
106-
`The error was thrown at: ${formattedCallStack}`,
107-
);
103+
let combinedMessage = `${componentNameMessage} You should fix this error in your code. ${errorBoundaryMessage}\n\n`;
104+
if (!shouldIgnoreErrorMessage) {
105+
combinedMessage += `${errorSummary}\n\n`;
106+
}
107+
combinedMessage +=
108+
`The error is located at: ${componentStack}\n\n` +
109+
`The error was thrown at: ${formattedCallStack}`;
110+
111+
console.error(combinedMessage);
108112
} else {
109113
console.error(
110114
`React caught an error thrown by one of your components.\n\n${error.stack}`,

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type CapturedError = {
2626
errorBoundaryFound: boolean,
2727
errorBoundaryName: string | null,
2828
willRetry: boolean,
29+
shouldIgnoreErrorMessage: boolean,
2930
};
3031

3132
export type HandleErrorInfo = {
@@ -1188,15 +1189,32 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
11881189
if (capturedErrors === null) {
11891190
capturedErrors = new Map();
11901191
}
1191-
capturedErrors.set(boundary, {
1192-
componentName,
1193-
componentStack,
1194-
error,
1195-
errorBoundary: errorBoundaryFound ? boundary.stateNode : null,
1196-
errorBoundaryFound,
1197-
errorBoundaryName,
1198-
willRetry,
1199-
});
1192+
1193+
if (__DEV__) {
1194+
capturedErrors.set(boundary, {
1195+
componentName,
1196+
componentStack,
1197+
error,
1198+
errorBoundary: errorBoundaryFound ? boundary.stateNode : null,
1199+
errorBoundaryFound,
1200+
errorBoundaryName,
1201+
willRetry,
1202+
shouldIgnoreErrorMessage: error != null &&
1203+
typeof error.__reactShouldIgnoreErrorMessage === 'boolean' &&
1204+
error.__reactShouldIgnoreErrorMessage,
1205+
});
1206+
} else {
1207+
capturedErrors.set(boundary, {
1208+
componentName,
1209+
componentStack,
1210+
error,
1211+
errorBoundary: errorBoundaryFound ? boundary.stateNode : null,
1212+
errorBoundaryFound,
1213+
errorBoundaryName,
1214+
willRetry,
1215+
shouldIgnoreErrorMessage: false,
1216+
});
1217+
}
12001218

12011219
// If we're in the commit phase, defer scheduling an update on the
12021220
// boundary until after the commit is complete

src/renderers/shared/utils/ReactErrorUtils.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,14 @@ if (__DEV__) {
208208
let error;
209209
// Use this to track whether the error event is ever called.
210210
let didSetError = false;
211+
let isCrossOriginError = false;
211212

212213
function onError(event) {
213214
error = event.error;
214215
didSetError = true;
216+
if (error === null && event.colno === 0 && event.lineno === 0) {
217+
isCrossOriginError = true;
218+
}
215219
}
216220

217221
// Create a fake event type.
@@ -240,6 +244,17 @@ if (__DEV__) {
240244
'or switching to a modern browser. If you suspect that this is ' +
241245
'actually an issue with React, please file an issue.',
242246
);
247+
} else if (isCrossOriginError) {
248+
error = new Error(
249+
"A cross-origin error was thrown. React doesn't have access to " +
250+
'the actual error because it catches errors using a global ' +
251+
'error handler, in order to preserve the "Pause on exceptions" ' +
252+
'behavior of the DevTools. This is only an issue in DEV-mode; ' +
253+
'in production, React uses a normal try-catch statement.\n\n' +
254+
"It's recommended to serve JavaScript files from the same " +
255+
'origin as your application.',
256+
);
257+
(error: any).__reactShouldIgnoreErrorMessage = true;
243258
}
244259
ReactErrorUtils._hasCaughtError = true;
245260
ReactErrorUtils._caughtError = error;

0 commit comments

Comments
 (0)