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 {checkFormFieldValueStringCoercion} from 'shared/CheckStringCoercion';
    
  11. 
    
  12. type ValueTracker = {
    
  13.   getValue(): string,
    
  14.   setValue(value: string): void,
    
  15.   stopTracking(): void,
    
  16. };
    
  17. interface ElementWithValueTracker extends HTMLInputElement {
    
  18.   _valueTracker?: ?ValueTracker;
    
  19. }
    
  20. 
    
  21. function isCheckable(elem: HTMLInputElement) {
    
  22.   const type = elem.type;
    
  23.   const nodeName = elem.nodeName;
    
  24.   return (
    
  25.     nodeName &&
    
  26.     nodeName.toLowerCase() === 'input' &&
    
  27.     (type === 'checkbox' || type === 'radio')
    
  28.   );
    
  29. }
    
  30. 
    
  31. function getTracker(node: ElementWithValueTracker) {
    
  32.   return node._valueTracker;
    
  33. }
    
  34. 
    
  35. function detachTracker(node: ElementWithValueTracker) {
    
  36.   node._valueTracker = null;
    
  37. }
    
  38. 
    
  39. function getValueFromNode(node: HTMLInputElement): string {
    
  40.   let value = '';
    
  41.   if (!node) {
    
  42.     return value;
    
  43.   }
    
  44. 
    
  45.   if (isCheckable(node)) {
    
  46.     value = node.checked ? 'true' : 'false';
    
  47.   } else {
    
  48.     value = node.value;
    
  49.   }
    
  50. 
    
  51.   return value;
    
  52. }
    
  53. 
    
  54. function trackValueOnNode(node: any): ?ValueTracker {
    
  55.   const valueField = isCheckable(node) ? 'checked' : 'value';
    
  56.   const descriptor = Object.getOwnPropertyDescriptor(
    
  57.     node.constructor.prototype,
    
  58.     valueField,
    
  59.   );
    
  60. 
    
  61.   if (__DEV__) {
    
  62.     checkFormFieldValueStringCoercion(node[valueField]);
    
  63.   }
    
  64.   let currentValue = '' + node[valueField];
    
  65. 
    
  66.   // if someone has already defined a value or Safari, then bail
    
  67.   // and don't track value will cause over reporting of changes,
    
  68.   // but it's better then a hard failure
    
  69.   // (needed for certain tests that spyOn input values and Safari)
    
  70.   if (
    
  71.     node.hasOwnProperty(valueField) ||
    
  72.     typeof descriptor === 'undefined' ||
    
  73.     typeof descriptor.get !== 'function' ||
    
  74.     typeof descriptor.set !== 'function'
    
  75.   ) {
    
  76.     return;
    
  77.   }
    
  78.   const {get, set} = descriptor;
    
  79.   Object.defineProperty(node, valueField, {
    
  80.     configurable: true,
    
  81.     // $FlowFixMe[missing-this-annot]
    
  82.     get: function () {
    
  83.       return get.call(this);
    
  84.     },
    
  85.     // $FlowFixMe[missing-local-annot]
    
  86.     // $FlowFixMe[missing-this-annot]
    
  87.     set: function (value) {
    
  88.       if (__DEV__) {
    
  89.         checkFormFieldValueStringCoercion(value);
    
  90.       }
    
  91.       currentValue = '' + value;
    
  92.       set.call(this, value);
    
  93.     },
    
  94.   });
    
  95.   // We could've passed this the first time
    
  96.   // but it triggers a bug in IE11 and Edge 14/15.
    
  97.   // Calling defineProperty() again should be equivalent.
    
  98.   // https://github.com/facebook/react/issues/11768
    
  99.   Object.defineProperty(node, valueField, {
    
  100.     enumerable: descriptor.enumerable,
    
  101.   });
    
  102. 
    
  103.   const tracker = {
    
  104.     getValue() {
    
  105.       return currentValue;
    
  106.     },
    
  107.     setValue(value: string) {
    
  108.       if (__DEV__) {
    
  109.         checkFormFieldValueStringCoercion(value);
    
  110.       }
    
  111.       currentValue = '' + value;
    
  112.     },
    
  113.     stopTracking() {
    
  114.       detachTracker(node);
    
  115.       delete node[valueField];
    
  116.     },
    
  117.   };
    
  118.   return tracker;
    
  119. }
    
  120. 
    
  121. export function track(node: ElementWithValueTracker) {
    
  122.   if (getTracker(node)) {
    
  123.     return;
    
  124.   }
    
  125. 
    
  126.   node._valueTracker = trackValueOnNode(node);
    
  127. }
    
  128. 
    
  129. export function updateValueIfChanged(node: ElementWithValueTracker): boolean {
    
  130.   if (!node) {
    
  131.     return false;
    
  132.   }
    
  133. 
    
  134.   const tracker = getTracker(node);
    
  135.   // if there is no tracker at this point it's unlikely
    
  136.   // that trying again will succeed
    
  137.   if (!tracker) {
    
  138.     return true;
    
  139.   }
    
  140. 
    
  141.   const lastValue = tracker.getValue();
    
  142.   const nextValue = getValueFromNode(node);
    
  143.   if (nextValue !== lastValue) {
    
  144.     tracker.setValue(nextValue);
    
  145.     return true;
    
  146.   }
    
  147.   return false;
    
  148. }
    
  149. 
    
  150. export function stopTracking(node: ElementWithValueTracker) {
    
  151.   const tracker = getTracker(node);
    
  152.   if (tracker) {
    
  153.     tracker.stopTracking();
    
  154.   }
    
  155. }