Fix development mode hang when iframe is removed (#19220)
* Fix development mode hang when iframe is removed * Also fix #16734
Dan Abramov committed
Jul 1, 2020 at 15:33 UTC
f918b0eb41e0eabb8e4dc2bcd20fb8a25654d68f
1 file changed
+33
-11
packages/shared/invokeGuardedCallbackImpl.js
+33
-11
@@ -9,7 +9,7 @@
9
10
import invariant from 'shared/invariant';
11
12
-let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(
12
+function invokeGuardedCallbackProd<A, B, C, D, E, F, Context>(
13
name: string | null,
14
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
15
context: Context,
@@ -26,7 +26,9 @@ let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(
26
} catch (error) {
27
this.onError(error);
28
}
29
-};
29
+}
30
+
31
+let invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
32
33
if (__DEV__) {
34
// In DEV mode, we swap out invokeGuardedCallback for a special version
@@ -58,7 +60,15 @@ if (__DEV__) {
60
) {
61
const fakeNode = document.createElement('react');
62
61
- const invokeGuardedCallbackDev = function<A, B, C, D, E, F, Context>(
63
+ invokeGuardedCallbackImpl = function invokeGuardedCallbackDev<
64
+ A,
65
+ B,
66
+ C,
67
+ D,
68
+ E,
69
+ F,
70
+ Context,
71
+ >(
72
name: string | null,
73
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
74
context: Context,
@@ -85,6 +95,7 @@ if (__DEV__) {
95
);
96
const evt = document.createEvent('Event');
97
98
+ let didCall = false;
99
// Keeps track of whether the user-provided callback threw an error. We
100
// set this to true at the beginning, then set it to false right after
101
// calling the function. If the function errors, `didError` will never be
@@ -105,11 +116,7 @@ if (__DEV__) {
116
'event',
117
);
118
108
- // Create an event handler for our fake event. We will synchronously
109
- // dispatch our fake event using `dispatchEvent`. Inside the handler, we
110
- // call the user-provided callback.
111
- const funcArgs = Array.prototype.slice.call(arguments, 3);
112
- function callCallback() {
119
+ function restoreAfterDispatch() {
120
// We immediately remove the callback from event listeners so that
121
// nested `invokeGuardedCallback` calls do not clash. Otherwise, a
122
// nested call would trigger the fake event handlers of any call higher
@@ -126,7 +133,15 @@ if (__DEV__) {
133
) {
134
window.event = windowEvent;
135
}
136
+ }
137
138
+ // Create an event handler for our fake event. We will synchronously
139
+ // dispatch our fake event using `dispatchEvent`. Inside the handler, we
140
+ // call the user-provided callback.
141
+ const funcArgs = Array.prototype.slice.call(arguments, 3);
142
+ function callCallback() {
143
+ didCall = true;
144
+ restoreAfterDispatch();
145
func.apply(context, funcArgs);
146
didError = false;
147
}
@@ -183,7 +198,7 @@ if (__DEV__) {
198
Object.defineProperty(window, 'event', windowEventDescriptor);
199
}
200
186
- if (didError) {
201
+ if (didCall && didError) {
202
if (!didSetError) {
203
// The callback errored, but the error event never fired.
204
error = new Error(
@@ -208,9 +223,16 @@ if (__DEV__) {
223
224
// Remove our event listeners
225
window.removeEventListener('error', handleWindowError);
211
- };
226
213
- invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
227
+ if (!didCall) {
228
+ // Something went really wrong, and our event was not dispatched.
229
+ // https://github.com/facebook/react/issues/16734
230
+ // https://github.com/facebook/react/issues/16585
231
+ // Fall back to the production implementation.
232
+ restoreAfterDispatch();
233
+ return invokeGuardedCallbackProd.apply(this, arguments);
234
+ }
235
+ };
236
}
237
}
238