1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. let fakeNode: Element = (null: any);
    
  11. if (__DEV__) {
    
  12.   if (
    
  13.     typeof window !== 'undefined' &&
    
  14.     typeof window.dispatchEvent === 'function' &&
    
  15.     typeof document !== 'undefined' &&
    
  16.     // $FlowFixMe[method-unbinding]
    
  17.     typeof document.createEvent === 'function'
    
  18.   ) {
    
  19.     fakeNode = document.createElement('react');
    
  20.   }
    
  21. }
    
  22. 
    
  23. export default function invokeGuardedCallbackImpl<Args: Array<mixed>, Context>(
    
  24.   this: {onError: (error: mixed) => void},
    
  25.   name: string | null,
    
  26.   func: (...Args) => mixed,
    
  27.   context: Context,
    
  28. ): void {
    
  29.   if (__DEV__) {
    
  30.     // In DEV mode, we use a special version
    
  31.     // that plays more nicely with the browser's DevTools. The idea is to preserve
    
  32.     // "Pause on exceptions" behavior. Because React wraps all user-provided
    
  33.     // functions in invokeGuardedCallback, and the production version of
    
  34.     // invokeGuardedCallback uses a try-catch, all user exceptions are treated
    
  35.     // like caught exceptions, and the DevTools won't pause unless the developer
    
  36.     // takes the extra step of enabling pause on caught exceptions. This is
    
  37.     // unintuitive, though, because even though React has caught the error, from
    
  38.     // the developer's perspective, the error is uncaught.
    
  39.     //
    
  40.     // To preserve the expected "Pause on exceptions" behavior, we don't use a
    
  41.     // try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
    
  42.     // DOM node, and call the user-provided callback from inside an event handler
    
  43.     // for that fake event. If the callback throws, the error is "captured" using
    
  44.     // event loop context, it does not interrupt the normal program flow.
    
  45.     // Effectively, this gives us try-catch behavior without actually using
    
  46.     // try-catch. Neat!
    
  47. 
    
  48.     // fakeNode signifies we are in an environment with a document and window object
    
  49.     if (fakeNode) {
    
  50.       const evt = document.createEvent('Event');
    
  51. 
    
  52.       let didCall = false;
    
  53.       // Keeps track of whether the user-provided callback threw an error. We
    
  54.       // set this to true at the beginning, then set it to false right after
    
  55.       // calling the function. If the function errors, `didError` will never be
    
  56.       // set to false. This strategy works even if the browser is flaky and
    
  57.       // fails to call our global error handler, because it doesn't rely on
    
  58.       // the error event at all.
    
  59.       let didError = true;
    
  60. 
    
  61.       // Keeps track of the value of window.event so that we can reset it
    
  62.       // during the callback to let user code access window.event in the
    
  63.       // browsers that support it.
    
  64.       const windowEvent = window.event;
    
  65. 
    
  66.       // Keeps track of the descriptor of window.event to restore it after event
    
  67.       // dispatching: https://github.com/facebook/react/issues/13688
    
  68.       const windowEventDescriptor = Object.getOwnPropertyDescriptor(
    
  69.         window,
    
  70.         'event',
    
  71.       );
    
  72. 
    
  73.       const restoreAfterDispatch = () => {
    
  74.         // We immediately remove the callback from event listeners so that
    
  75.         // nested `invokeGuardedCallback` calls do not clash. Otherwise, a
    
  76.         // nested call would trigger the fake event handlers of any call higher
    
  77.         // in the stack.
    
  78.         fakeNode.removeEventListener(evtType, callCallback, false);
    
  79. 
    
  80.         // We check for window.hasOwnProperty('event') to prevent the
    
  81.         // window.event assignment in both IE <= 10 as they throw an error
    
  82.         // "Member not found" in strict mode, and in Firefox which does not
    
  83.         // support window.event.
    
  84.         if (
    
  85.           typeof window.event !== 'undefined' &&
    
  86.           window.hasOwnProperty('event')
    
  87.         ) {
    
  88.           window.event = windowEvent;
    
  89.         }
    
  90.       };
    
  91. 
    
  92.       // Create an event handler for our fake event. We will synchronously
    
  93.       // dispatch our fake event using `dispatchEvent`. Inside the handler, we
    
  94.       // call the user-provided callback.
    
  95.       // $FlowFixMe[method-unbinding]
    
  96.       const funcArgs = Array.prototype.slice.call(arguments, 3);
    
  97.       const callCallback = () => {
    
  98.         didCall = true;
    
  99.         restoreAfterDispatch();
    
  100.         // $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.
    
  101.         func.apply(context, funcArgs);
    
  102.         didError = false;
    
  103.       };
    
  104. 
    
  105.       // Create a global error event handler. We use this to capture the value
    
  106.       // that was thrown. It's possible that this error handler will fire more
    
  107.       // than once; for example, if non-React code also calls `dispatchEvent`
    
  108.       // and a handler for that event throws. We should be resilient to most of
    
  109.       // those cases. Even if our error event handler fires more than once, the
    
  110.       // last error event is always used. If the callback actually does error,
    
  111.       // we know that the last error event is the correct one, because it's not
    
  112.       // possible for anything else to have happened in between our callback
    
  113.       // erroring and the code that follows the `dispatchEvent` call below. If
    
  114.       // the callback doesn't error, but the error event was fired, we know to
    
  115.       // ignore it because `didError` will be false, as described above.
    
  116.       let error;
    
  117.       // Use this to track whether the error event is ever called.
    
  118.       let didSetError = false;
    
  119.       let isCrossOriginError = false;
    
  120. 
    
  121.       const handleWindowError = (event: ErrorEvent) => {
    
  122.         error = event.error;
    
  123.         didSetError = true;
    
  124.         if (error === null && event.colno === 0 && event.lineno === 0) {
    
  125.           isCrossOriginError = true;
    
  126.         }
    
  127.         if (event.defaultPrevented) {
    
  128.           // Some other error handler has prevented default.
    
  129.           // Browsers silence the error report if this happens.
    
  130.           // We'll remember this to later decide whether to log it or not.
    
  131.           if (error != null && typeof error === 'object') {
    
  132.             try {
    
  133.               error._suppressLogging = true;
    
  134.             } catch (inner) {
    
  135.               // Ignore.
    
  136.             }
    
  137.           }
    
  138.         }
    
  139.       };
    
  140. 
    
  141.       // Create a fake event type.
    
  142.       const evtType = `react-${name ? name : 'invokeguardedcallback'}`;
    
  143. 
    
  144.       // Attach our event handlers
    
  145.       window.addEventListener('error', handleWindowError);
    
  146.       fakeNode.addEventListener(evtType, callCallback, false);
    
  147. 
    
  148.       // Synchronously dispatch our fake event. If the user-provided function
    
  149.       // errors, it will trigger our global error handler.
    
  150.       evt.initEvent(evtType, false, false);
    
  151.       fakeNode.dispatchEvent(evt);
    
  152.       if (windowEventDescriptor) {
    
  153.         Object.defineProperty(window, 'event', windowEventDescriptor);
    
  154.       }
    
  155. 
    
  156.       if (didCall && didError) {
    
  157.         if (!didSetError) {
    
  158.           // The callback errored, but the error event never fired.
    
  159.           // eslint-disable-next-line react-internal/prod-error-codes
    
  160.           error = new Error(
    
  161.             'An error was thrown inside one of your components, but React ' +
    
  162.               "doesn't know what it was. This is likely due to browser " +
    
  163.               'flakiness. React does its best to preserve the "Pause on ' +
    
  164.               'exceptions" behavior of the DevTools, which requires some ' +
    
  165.               "DEV-mode only tricks. It's possible that these don't work in " +
    
  166.               'your browser. Try triggering the error in production mode, ' +
    
  167.               'or switching to a modern browser. If you suspect that this is ' +
    
  168.               'actually an issue with React, please file an issue.',
    
  169.           );
    
  170.         } else if (isCrossOriginError) {
    
  171.           // eslint-disable-next-line react-internal/prod-error-codes
    
  172.           error = new Error(
    
  173.             "A cross-origin error was thrown. React doesn't have access to " +
    
  174.               'the actual error object in development. ' +
    
  175.               'See https://reactjs.org/link/crossorigin-error for more information.',
    
  176.           );
    
  177.         }
    
  178.         this.onError(error);
    
  179.       }
    
  180. 
    
  181.       // Remove our event listeners
    
  182.       window.removeEventListener('error', handleWindowError);
    
  183. 
    
  184.       if (didCall) {
    
  185.         return;
    
  186.       } else {
    
  187.         // Something went really wrong, and our event was not dispatched.
    
  188.         // https://github.com/facebook/react/issues/16734
    
  189.         // https://github.com/facebook/react/issues/16585
    
  190.         // Fall back to the production implementation.
    
  191.         restoreAfterDispatch();
    
  192.         // we fall through and call the prod version instead
    
  193.       }
    
  194.     }
    
  195.     // We only get here if we are in an environment that either does not support the browser
    
  196.     // variant or we had trouble getting the browser to emit the error.
    
  197.     // $FlowFixMe[method-unbinding]
    
  198.     const funcArgs = Array.prototype.slice.call(arguments, 3);
    
  199.     try {
    
  200.       // $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.
    
  201.       func.apply(context, funcArgs);
    
  202.     } catch (error) {
    
  203.       this.onError(error);
    
  204.     }
    
  205.   } else {
    
  206.     // $FlowFixMe[method-unbinding]
    
  207.     const funcArgs = Array.prototype.slice.call(arguments, 3);
    
  208.     try {
    
  209.       // $FlowFixMe[incompatible-call] Flow doesn't understand the arguments splicing.
    
  210.       func.apply(context, funcArgs);
    
  211.     } catch (error) {
    
  212.       this.onError(error);
    
  213.     }
    
  214.   }
    
  215. }