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. import type {AnyNativeEvent} from '../PluginModuleType';
    
  11. import type {DOMEventName} from '../DOMEventNames';
    
  12. import type {DispatchQueue} from '../DOMPluginEventSystem';
    
  13. import type {EventSystemFlags} from '../EventSystemFlags';
    
  14. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  15. import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
    
  16. 
    
  17. import {getFiberCurrentPropsFromNode} from '../../client/ReactDOMComponentTree';
    
  18. import {startHostTransition} from 'react-reconciler/src/ReactFiberReconciler';
    
  19. 
    
  20. import {SyntheticEvent} from '../SyntheticEvent';
    
  21. 
    
  22. /**
    
  23.  * This plugin invokes action functions on forms, inputs and buttons if
    
  24.  * the form doesn't prevent default.
    
  25.  */
    
  26. function extractEvents(
    
  27.   dispatchQueue: DispatchQueue,
    
  28.   domEventName: DOMEventName,
    
  29.   maybeTargetInst: null | Fiber,
    
  30.   nativeEvent: AnyNativeEvent,
    
  31.   nativeEventTarget: null | EventTarget,
    
  32.   eventSystemFlags: EventSystemFlags,
    
  33.   targetContainer: EventTarget,
    
  34. ) {
    
  35.   if (domEventName !== 'submit') {
    
  36.     return;
    
  37.   }
    
  38.   if (!maybeTargetInst || maybeTargetInst.stateNode !== nativeEventTarget) {
    
  39.     // If we're inside a parent root that itself is a parent of this root, then
    
  40.     // its deepest target won't be the actual form that's being submitted.
    
  41.     return;
    
  42.   }
    
  43.   const formInst = maybeTargetInst;
    
  44.   const form: HTMLFormElement = (nativeEventTarget: any);
    
  45.   let action = (getFiberCurrentPropsFromNode(form): any).action;
    
  46.   let submitter: null | HTMLInputElement | HTMLButtonElement =
    
  47.     (nativeEvent: any).submitter;
    
  48.   let submitterAction;
    
  49.   if (submitter) {
    
  50.     const submitterProps = getFiberCurrentPropsFromNode(submitter);
    
  51.     submitterAction = submitterProps
    
  52.       ? (submitterProps: any).formAction
    
  53.       : submitter.getAttribute('formAction');
    
  54.     if (submitterAction != null) {
    
  55.       // The submitter overrides the form action.
    
  56.       action = submitterAction;
    
  57.       // If the action is a function, we don't want to pass its name
    
  58.       // value to the FormData since it's controlled by the server.
    
  59.       submitter = null;
    
  60.     }
    
  61.   }
    
  62. 
    
  63.   if (typeof action !== 'function') {
    
  64.     return;
    
  65.   }
    
  66. 
    
  67.   const event = new SyntheticEvent(
    
  68.     'action',
    
  69.     'action',
    
  70.     null,
    
  71.     nativeEvent,
    
  72.     nativeEventTarget,
    
  73.   );
    
  74. 
    
  75.   function submitForm() {
    
  76.     if (nativeEvent.defaultPrevented) {
    
  77.       // We let earlier events to prevent the action from submitting.
    
  78.       return;
    
  79.     }
    
  80.     // Prevent native navigation.
    
  81.     event.preventDefault();
    
  82.     let formData;
    
  83.     if (submitter) {
    
  84.       // The submitter's value should be included in the FormData.
    
  85.       // It should be in the document order in the form.
    
  86.       // Since the FormData constructor invokes the formdata event it also
    
  87.       // needs to be available before that happens so after construction it's too
    
  88.       // late. We use a temporary fake node for the duration of this event.
    
  89.       // TODO: FormData takes a second argument that it's the submitter but this
    
  90.       // is fairly new so not all browsers support it yet. Switch to that technique
    
  91.       // when available.
    
  92.       const temp = submitter.ownerDocument.createElement('input');
    
  93.       temp.name = submitter.name;
    
  94.       temp.value = submitter.value;
    
  95.       (submitter.parentNode: any).insertBefore(temp, submitter);
    
  96.       formData = new FormData(form);
    
  97.       (temp.parentNode: any).removeChild(temp);
    
  98.     } else {
    
  99.       formData = new FormData(form);
    
  100.     }
    
  101. 
    
  102.     const pendingState: FormStatus = {
    
  103.       pending: true,
    
  104.       data: formData,
    
  105.       method: form.method,
    
  106.       action: action,
    
  107.     };
    
  108.     if (__DEV__) {
    
  109.       Object.freeze(pendingState);
    
  110.     }
    
  111.     startHostTransition(formInst, pendingState, action, formData);
    
  112.   }
    
  113. 
    
  114.   dispatchQueue.push({
    
  115.     event,
    
  116.     listeners: [
    
  117.       {
    
  118.         instance: null,
    
  119.         listener: submitForm,
    
  120.         currentTarget: form,
    
  121.       },
    
  122.     ],
    
  123.   });
    
  124. }
    
  125. 
    
  126. export {extractEvents};
    
  127. 
    
  128. export function dispatchReplayedFormAction(
    
  129.   formInst: Fiber,
    
  130.   form: HTMLFormElement,
    
  131.   action: FormData => void | Promise<void>,
    
  132.   formData: FormData,
    
  133. ): void {
    
  134.   const pendingState: FormStatus = {
    
  135.     pending: true,
    
  136.     data: formData,
    
  137.     method: form.method,
    
  138.     action: action,
    
  139.   };
    
  140.   if (__DEV__) {
    
  141.     Object.freeze(pendingState);
    
  142.   }
    
  143.   startHostTransition(formInst, pendingState, action, formData);
    
  144. }