Skip to content

Commit

Permalink
[Fiber] InvokeGuardedCallback without metaprogramming (#26569)
Browse files Browse the repository at this point in the history
InvokeGuardedCallback is now implemented with the browser fork done at
error-time rather than module-load-time. Originally it also tried to
freeze the window/document references to avoid mismatches in prototype
chains when testing React in different documents however we have since
updated our tests to not do this and it was a test only feature so I
removed it.

DiffTrain build for commit cc93a85.
  • Loading branch information
gnoff committed Apr 20, 2023
1 parent ce24091 commit 47d848a
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<43355784ad4a055ab0a02e5c5e648c88>>
* @generated SignedSource<<a8437bee4ca7ca2a6e95eed698390cea>>
*/

'use strict';
Expand Down Expand Up @@ -16152,71 +16152,40 @@ function unwindInterruptedWork(current, interruptedWork, renderLanes) {
}
}

// $FlowFixMe[missing-this-annot]
function invokeGuardedCallbackProd(name, func, context) {
// $FlowFixMe[method-unbinding]
var funcArgs = Array.prototype.slice.call(arguments, 3);

try {
// $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
}

var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
var fakeNode = null;

{
// In DEV mode, we swap out invokeGuardedCallback for a special version
// that plays more nicely with the browser's DevTools. The idea is to preserve
// "Pause on exceptions" behavior. Because React wraps all user-provided
// functions in invokeGuardedCallback, and the production version of
// invokeGuardedCallback uses a try-catch, all user exceptions are treated
// like caught exceptions, and the DevTools won't pause unless the developer
// takes the extra step of enabling pause on caught exceptions. This is
// unintuitive, though, because even though React has caught the error, from
// the developer's perspective, the error is uncaught.
//
// To preserve the expected "Pause on exceptions" behavior, we don't use a
// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
// DOM node, and call the user-provided callback from inside an event handler
// for that fake event. If the callback throws, the error is "captured" using
// a global event handler. But because the error happens in a different
// event loop context, it does not interrupt the normal program flow.
// Effectively, this gives us try-catch behavior without actually using
// try-catch. Neat!
// Check that the browser supports the APIs we need to implement our special
// DEV version of invokeGuardedCallback
if (
typeof window !== "undefined" &&
typeof window.dispatchEvent === "function" &&
typeof document !== "undefined" && // $FlowFixMe[method-unbinding]
typeof document.createEvent === "function"
) {
var fakeNode = document.createElement("react");

invokeGuardedCallbackImpl = function invokeGuardedCallbackDev(
name,
func,
context
) {
// If document doesn't exist we know for sure we will crash in this method
// when we call document.createEvent(). However this can cause confusing
// errors: https://github.com/facebook/create-react-app/issues/3482
// So we preemptively throw with a better message instead.
if (typeof document === "undefined" || document === null) {
throw new Error(
"The `document` global was defined when React was initialized, but is not " +
"defined anymore. This can happen in a test environment if a component " +
"schedules an update from an asynchronous callback, but the test has already " +
"finished running. To solve this, you can either unmount the component at " +
"the end of your test (and ensure that any asynchronous operations get " +
"canceled in `componentWillUnmount`), or you can change the test itself " +
"to be asynchronous."
);
}
fakeNode = document.createElement("react");
}
}

function invokeGuardedCallbackImpl(name, func, context) {
{
// In DEV mode, we use a special version
// that plays more nicely with the browser's DevTools. The idea is to preserve
// "Pause on exceptions" behavior. Because React wraps all user-provided
// functions in invokeGuardedCallback, and the production version of
// invokeGuardedCallback uses a try-catch, all user exceptions are treated
// like caught exceptions, and the DevTools won't pause unless the developer
// takes the extra step of enabling pause on caught exceptions. This is
// unintuitive, though, because even though React has caught the error, from
// the developer's perspective, the error is uncaught.
//
// To preserve the expected "Pause on exceptions" behavior, we don't use a
// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
// DOM node, and call the user-provided callback from inside an event handler
// for that fake event. If the callback throws, the error is "captured" using
// event loop context, it does not interrupt the normal program flow.
// Effectively, this gives us try-catch behavior without actually using
// try-catch. Neat!
// fakeNode signifies we are in an environment with a document and window object
if (fakeNode) {
var evt = document.createEvent("Event");
var 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
Expand All @@ -16237,7 +16206,7 @@ var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
"event"
);

function restoreAfterDispatch() {
var restoreAfterDispatch = function () {
// 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
Expand All @@ -16253,20 +16222,20 @@ var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
) {
window.event = windowEvent;
}
} // Create an event handler for our fake event. We will synchronously
}; // 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.
// $FlowFixMe[method-unbinding]

var funcArgs = Array.prototype.slice.call(arguments, 3);
var _funcArgs = Array.prototype.slice.call(arguments, 3);

function callCallback() {
var callCallback = function () {
didCall = true;
restoreAfterDispatch(); // $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.

func.apply(context, funcArgs);
func.apply(context, _funcArgs);
didError = false;
} // Create a global error event handler. We use this to capture the value
}; // Create a global error event handler. We use this to capture the value
// that was thrown. It's possible that this error handler will fire more
// than once; for example, if non-React code also calls `dispatchEvent`
// and a handler for that event throws. We should be resilient to most of
Expand All @@ -16281,9 +16250,9 @@ var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
var error; // Use this to track whether the error event is ever called.

var didSetError = false;
var isCrossOriginError = false; // $FlowFixMe[missing-local-annot]
var isCrossOriginError = false;

function handleWindowError(event) {
var handleWindowError = function (event) {
error = event.error;
didSetError = true;

Expand All @@ -16303,7 +16272,7 @@ var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
}
}
}
} // Create a fake event type.
}; // Create a fake event type.

var evtType = "react-" + (name ? name : "invokeguardedcallback"); // Attach our event handlers

Expand Down Expand Up @@ -16346,20 +16315,30 @@ var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;

window.removeEventListener("error", handleWindowError);

if (!didCall) {
if (didCall) {
return;
} else {
// 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);
restoreAfterDispatch(); // we fall through and call the prod version instead
}
};
} // We only get here if we are in an environment that either does not support the browser
// variant or we had trouble getting the browser to emit the error.
// $FlowFixMe[method-unbinding]

var funcArgs = Array.prototype.slice.call(arguments, 3);

try {
// $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
}
}

var invokeGuardedCallbackImpl$1 = invokeGuardedCallbackImpl;

var hasError = false;
var caughtError = null; // Used by event system to capture/rethrow the first error.
var reporter = {
Expand All @@ -16385,7 +16364,7 @@ var reporter = {
function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
hasError = false;
caughtError = null;
invokeGuardedCallbackImpl$1.apply(reporter, arguments);
invokeGuardedCallbackImpl.apply(reporter, arguments);
}
function clearCaughtError() {
if (hasError) {
Expand Down Expand Up @@ -23859,7 +23838,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-next-fdad813ac-20230420";
var ReactVersion = "18.3.0-next-cc93a8533-20230420";

// Might add PROFILE later.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8599,7 +8599,7 @@ var devToolsConfig$jscomp$inline_1021 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-next-fdad813ac-20230420",
version: "18.3.0-next-cc93a8533-20230420",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1206 = {
Expand Down Expand Up @@ -8630,7 +8630,7 @@ var internals$jscomp$inline_1206 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-next-fdad813ac-20230420"
reconcilerVersion: "18.3.0-next-cc93a8533-20230420"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1207 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9025,7 +9025,7 @@ var devToolsConfig$jscomp$inline_1063 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-next-fdad813ac-20230420",
version: "18.3.0-next-cc93a8533-20230420",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1247 = {
Expand Down Expand Up @@ -9056,7 +9056,7 @@ var internals$jscomp$inline_1247 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-next-fdad813ac-20230420"
reconcilerVersion: "18.3.0-next-cc93a8533-20230420"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1248 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if (
}
"use strict";

var ReactVersion = "18.3.0-next-fdad813ac-20230420";
var ReactVersion = "18.3.0-next-cc93a8533-20230420";

// ATTENTION
// When adding new symbols to this file,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,4 +639,4 @@ exports.useSyncExternalStore = function (
);
};
exports.useTransition = useTransition;
exports.version = "18.3.0-next-fdad813ac-20230420";
exports.version = "18.3.0-next-cc93a8533-20230420";
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ exports.useSyncExternalStore = function (
);
};
exports.useTransition = useTransition;
exports.version = "18.3.0-next-fdad813ac-20230420";
exports.version = "18.3.0-next-cc93a8533-20230420";

/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
fdad813ac765e901e2957b8d36fba87e5504e5f4
cc93a8533264618b7a8e3d6fb56df917214c19d7
Loading

0 comments on commit 47d848a

Please sign in to comment.