Repository: facebook/react
File: packages/shared/invokeGuardedCallbackImpl.js (JavaScript)

Code before:
          if (error != null && typeof error === 'object') {
            try {
              error._suppressLogging = true;
            } catch (inner) {
              // Ignore.
            }
          }
        }
      };

      // Create a fake event type.
      const evtType = `react-${name ? name : 'invokeguardedcallback'}`;

      // Attach our event handlers
      window.addEventListener('error', handleWindowError);
      fakeNode.addEventListener(evtType, callCallback, false);

      // Synchronously dispatch our fake event. If the user-provided function
      // errors, it will trigger our global error handler.
      evt.initEvent(evtType, false, false);
      try {
        fakeNode.dispatchEvent(evt);
      } catch (dispatchError) {
        // We don't expect the dispatchEvent to throw directly. If it does something went
        // terribly wrong. Since we are in the middle of resolving a different error we
        // suppress this one and let the original error be handled by React.
      } finally {
        if (windowEventDescriptor) {
          Object.defineProperty(window, 'event', windowEventDescriptor);
        }

        if (didCall && didError) {
          if (!didSetError) {
            // The callback errored, but the error event never fired.
            // eslint-disable-next-line react-internal/prod-error-codes
            error = new Error(
              'An error was thrown inside one of your components, but React ' +
                "doesn't know what it was. This is likely due to browser " +
                'flakiness. React does its best to preserve the "Pause on ' +
                'exceptions" behavior of the DevTools, which requires some ' +
                "DEV-mode only tricks. It's possible that these don't work in " +
                'your browser. Try triggering the error in production mode, ' +
                'or switching to a modern browser. If you suspect that this is ' +
                'actually an issue with React, please file an issue.',
            );
          } else if (isCrossOriginError) {
            // eslint-disable-next-line react-internal/prod-error-codes
            error = new Error(
              "A cross-origin error was thrown. React doesn't have access to " +
                'the actual error object in development. ' +
                'See https://reactjs.org/link/crossorigin-error for more information.',

Review comment (suggestion):
@sophiebits you were right, this isn't going to work to still preserve pause behavior. It's a bit baffling b/c the dispatchEvent doesn't throw the inner error but the presence of the catch must signal to devtools that the error was handled (even though it wasn't, it still reports as an uncaught error in the console)

If I extrapolate this though it doesn't make sense that this technique ever worked b/c there is always a catch block somewhere above invokeGuardedCallback so the dispatch is always done in some scope with a catch.

I did a little testing using 18.2 and could not get pause on uncaught exceptions to pause when rendering a component that Errors. Perhaps Chrome changed something and so maybe this used to work and now no longer does b/c browser changes but I'm really not sure

Code after:
      // call the user-provided callback.
      // $FlowFixMe[method-unbinding]
      const funcArgs = Array.prototype.slice.call(arguments, 3);
      function callCallback() {
        didCall = true;
        restoreAfterDispatch();
        // $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.
        func.apply(context, funcArgs);
        didError = false;
      }

      // 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
      // those cases. Even if our error event handler fires more than once, the
      // last error event is always used. If the callback actually does error,
      // we know that the last error event is the correct one, because it's not
      // possible for anything else to have happened in between our callback
      // erroring and the code that follows the `dispatchEvent` call below. If
      // the callback doesn't error, but the error event was fired, we know to
      // ignore it because `didError` will be false, as described above.
      let error;
      // Use this to track whether the error event is ever called.
      let didSetError = false;
      let isCrossOriginError = false;

      // $FlowFixMe[missing-local-annot]
      function handleWindowError(event) {
        error = event.error;
        didSetError = true;
        if (error === null && event.colno === 0 && event.lineno === 0) {
          isCrossOriginError = true;
        }
        if (event.defaultPrevented) {
          // Some other error handler has prevented default.
          // Browsers silence the error report if this happens.
          // We'll remember this to later decide whether to log it or not.
          if (error != null && typeof error === 'object') {
            try {
              error._suppressLogging = true;
            } catch (inner) {
              // Ignore.
            }
          }
        }
      }

      // Create a fake event type.
      const evtType = `react-${name ? name : 'invokeguardedcallback'}`;