diff --git a/packages/shared/invokeGuardedCallbackImpl.js b/packages/shared/invokeGuardedCallbackImpl.js index 8ba03aa8d9dd6..60e32e0a55287 100644 --- a/packages/shared/invokeGuardedCallbackImpl.js +++ b/packages/shared/invokeGuardedCallbackImpl.js @@ -9,7 +9,7 @@ import invariant from 'shared/invariant'; -let invokeGuardedCallbackImpl = function( +function invokeGuardedCallbackProd( name: string | null, func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed, context: Context, @@ -26,7 +26,9 @@ let invokeGuardedCallbackImpl = function( } catch (error) { this.onError(error); } -}; +} + +let invokeGuardedCallbackImpl = invokeGuardedCallbackProd; if (__DEV__) { // In DEV mode, we swap out invokeGuardedCallback for a special version @@ -58,7 +60,15 @@ if (__DEV__) { ) { const fakeNode = document.createElement('react'); - const invokeGuardedCallbackDev = function( + invokeGuardedCallbackImpl = function invokeGuardedCallbackDev< + A, + B, + C, + D, + E, + F, + Context, + >( name: string | null, func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed, context: Context, @@ -85,6 +95,7 @@ if (__DEV__) { ); const evt = document.createEvent('Event'); + let didCall = false; // Keeps track of whether the user-provided callback threw an error. We // set this to true at the beginning, then set it to false right after // calling the function. If the function errors, `didError` will never be @@ -105,11 +116,7 @@ if (__DEV__) { 'event', ); - // Create an event handler for our fake event. We will synchronously - // dispatch our fake event using `dispatchEvent`. Inside the handler, we - // call the user-provided callback. - const funcArgs = Array.prototype.slice.call(arguments, 3); - function callCallback() { + function restoreAfterDispatch() { // We immediately remove the callback from event listeners so that // nested `invokeGuardedCallback` calls do not clash. Otherwise, a // nested call would trigger the fake event handlers of any call higher @@ -126,7 +133,15 @@ if (__DEV__) { ) { window.event = windowEvent; } + } + // Create an event handler for our fake event. We will synchronously + // dispatch our fake event using `dispatchEvent`. Inside the handler, we + // call the user-provided callback. + const funcArgs = Array.prototype.slice.call(arguments, 3); + function callCallback() { + didCall = true; + restoreAfterDispatch(); func.apply(context, funcArgs); didError = false; } @@ -183,7 +198,7 @@ if (__DEV__) { Object.defineProperty(window, 'event', windowEventDescriptor); } - if (didError) { + if (didCall && didError) { if (!didSetError) { // The callback errored, but the error event never fired. error = new Error( @@ -208,9 +223,16 @@ if (__DEV__) { // Remove our event listeners window.removeEventListener('error', handleWindowError); - }; - invokeGuardedCallbackImpl = invokeGuardedCallbackDev; + if (!didCall) { + // Something went really wrong, and our event was not dispatched. + // https://github.com/facebook/react/issues/16734 + // https://github.com/facebook/react/issues/16585 + // Fall back to the production implementation. + restoreAfterDispatch(); + return invokeGuardedCallbackProd.apply(this, arguments); + } + }; } }