From 10ae68a73cb57132651b0f698890e787386d6b5f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 30 Jun 2020 16:08:49 +0100 Subject: [PATCH 1/2] Fix development mode hang when iframe is removed --- packages/shared/invokeGuardedCallbackImpl.js | 28 ++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/shared/invokeGuardedCallbackImpl.js b/packages/shared/invokeGuardedCallbackImpl.js index 8ba03aa8d9dd..117663abc1f8 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 @@ -110,6 +121,7 @@ if (__DEV__) { // call the user-provided callback. const funcArgs = Array.prototype.slice.call(arguments, 3); function callCallback() { + didCall = true; // 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 @@ -208,9 +220,15 @@ 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. + return invokeGuardedCallbackProd.apply(this, arguments); + } + }; } } From af5748830ecdf9add29dcb92c5db39015b2397ed Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 30 Jun 2020 16:32:11 +0100 Subject: [PATCH 2/2] Also fix #16734 --- packages/shared/invokeGuardedCallbackImpl.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/shared/invokeGuardedCallbackImpl.js b/packages/shared/invokeGuardedCallbackImpl.js index 117663abc1f8..60e32e0a5528 100644 --- a/packages/shared/invokeGuardedCallbackImpl.js +++ b/packages/shared/invokeGuardedCallbackImpl.js @@ -116,12 +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() { - didCall = true; + 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 @@ -138,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; } @@ -195,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( @@ -226,6 +229,7 @@ if (__DEV__) { // 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); } };