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 isAttributeNameSafe from '../shared/isAttributeNameSafe';
    
  11. import {
    
  12.   enableTrustedTypesIntegration,
    
  13.   enableCustomElementPropertySupport,
    
  14. } from 'shared/ReactFeatureFlags';
    
  15. import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
    
  16. import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';
    
  17. 
    
  18. /**
    
  19.  * Get the value for a attribute on a node. Only used in DEV for SSR validation.
    
  20.  * The third argument is used as a hint of what the expected value is. Some
    
  21.  * attributes have multiple equivalent values.
    
  22.  */
    
  23. export function getValueForAttribute(
    
  24.   node: Element,
    
  25.   name: string,
    
  26.   expected: mixed,
    
  27. ): mixed {
    
  28.   if (__DEV__) {
    
  29.     if (!isAttributeNameSafe(name)) {
    
  30.       return;
    
  31.     }
    
  32.     if (!node.hasAttribute(name)) {
    
  33.       // shouldRemoveAttribute
    
  34.       switch (typeof expected) {
    
  35.         case 'function':
    
  36.         case 'symbol': // eslint-disable-line
    
  37.           return expected;
    
  38.         case 'boolean': {
    
  39.           const prefix = name.toLowerCase().slice(0, 5);
    
  40.           if (prefix !== 'data-' && prefix !== 'aria-') {
    
  41.             return expected;
    
  42.           }
    
  43.         }
    
  44.       }
    
  45.       return expected === undefined ? undefined : null;
    
  46.     }
    
  47.     const value = node.getAttribute(name);
    
  48.     if (__DEV__) {
    
  49.       checkAttributeStringCoercion(expected, name);
    
  50.     }
    
  51.     if (value === '' + (expected: any)) {
    
  52.       return expected;
    
  53.     }
    
  54.     return value;
    
  55.   }
    
  56. }
    
  57. 
    
  58. export function getValueForAttributeOnCustomComponent(
    
  59.   node: Element,
    
  60.   name: string,
    
  61.   expected: mixed,
    
  62. ): mixed {
    
  63.   if (__DEV__) {
    
  64.     if (!isAttributeNameSafe(name)) {
    
  65.       return;
    
  66.     }
    
  67.     if (!node.hasAttribute(name)) {
    
  68.       // shouldRemoveAttribute
    
  69.       switch (typeof expected) {
    
  70.         case 'symbol':
    
  71.         case 'object':
    
  72.           // Symbols and objects are ignored when they're emitted so
    
  73.           // it would be expected that they end up not having an attribute.
    
  74.           return expected;
    
  75.         case 'function':
    
  76.           if (enableCustomElementPropertySupport) {
    
  77.             return expected;
    
  78.           }
    
  79.           break;
    
  80.         case 'boolean':
    
  81.           if (enableCustomElementPropertySupport) {
    
  82.             if (expected === false) {
    
  83.               return expected;
    
  84.             }
    
  85.           }
    
  86.       }
    
  87.       return expected === undefined ? undefined : null;
    
  88.     }
    
  89.     const value = node.getAttribute(name);
    
  90. 
    
  91.     if (enableCustomElementPropertySupport) {
    
  92.       if (value === '' && expected === true) {
    
  93.         return true;
    
  94.       }
    
  95.     }
    
  96. 
    
  97.     if (__DEV__) {
    
  98.       checkAttributeStringCoercion(expected, name);
    
  99.     }
    
  100.     if (value === '' + (expected: any)) {
    
  101.       return expected;
    
  102.     }
    
  103.     return value;
    
  104.   }
    
  105. }
    
  106. 
    
  107. export function setValueForAttribute(
    
  108.   node: Element,
    
  109.   name: string,
    
  110.   value: mixed,
    
  111. ) {
    
  112.   if (isAttributeNameSafe(name)) {
    
  113.     // If the prop isn't in the special list, treat it as a simple attribute.
    
  114.     // shouldRemoveAttribute
    
  115.     if (value === null) {
    
  116.       node.removeAttribute(name);
    
  117.       return;
    
  118.     }
    
  119.     switch (typeof value) {
    
  120.       case 'undefined':
    
  121.       case 'function':
    
  122.       case 'symbol': // eslint-disable-line
    
  123.         node.removeAttribute(name);
    
  124.         return;
    
  125.       case 'boolean': {
    
  126.         const prefix = name.toLowerCase().slice(0, 5);
    
  127.         if (prefix !== 'data-' && prefix !== 'aria-') {
    
  128.           node.removeAttribute(name);
    
  129.           return;
    
  130.         }
    
  131.       }
    
  132.     }
    
  133.     if (__DEV__) {
    
  134.       checkAttributeStringCoercion(value, name);
    
  135.     }
    
  136.     node.setAttribute(
    
  137.       name,
    
  138.       enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
    
  139.     );
    
  140.   }
    
  141. }
    
  142. 
    
  143. export function setValueForKnownAttribute(
    
  144.   node: Element,
    
  145.   name: string,
    
  146.   value: mixed,
    
  147. ) {
    
  148.   if (value === null) {
    
  149.     node.removeAttribute(name);
    
  150.     return;
    
  151.   }
    
  152.   switch (typeof value) {
    
  153.     case 'undefined':
    
  154.     case 'function':
    
  155.     case 'symbol':
    
  156.     case 'boolean': {
    
  157.       node.removeAttribute(name);
    
  158.       return;
    
  159.     }
    
  160.   }
    
  161.   if (__DEV__) {
    
  162.     checkAttributeStringCoercion(value, name);
    
  163.   }
    
  164.   node.setAttribute(
    
  165.     name,
    
  166.     enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
    
  167.   );
    
  168. }
    
  169. 
    
  170. export function setValueForNamespacedAttribute(
    
  171.   node: Element,
    
  172.   namespace: string,
    
  173.   name: string,
    
  174.   value: mixed,
    
  175. ) {
    
  176.   if (value === null) {
    
  177.     node.removeAttribute(name);
    
  178.     return;
    
  179.   }
    
  180.   switch (typeof value) {
    
  181.     case 'undefined':
    
  182.     case 'function':
    
  183.     case 'symbol':
    
  184.     case 'boolean': {
    
  185.       node.removeAttribute(name);
    
  186.       return;
    
  187.     }
    
  188.   }
    
  189.   if (__DEV__) {
    
  190.     checkAttributeStringCoercion(value, name);
    
  191.   }
    
  192.   node.setAttributeNS(
    
  193.     namespace,
    
  194.     name,
    
  195.     enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
    
  196.   );
    
  197. }
    
  198. 
    
  199. export function setValueForPropertyOnCustomComponent(
    
  200.   node: Element,
    
  201.   name: string,
    
  202.   value: mixed,
    
  203. ) {
    
  204.   if (name[0] === 'o' && name[1] === 'n') {
    
  205.     const useCapture = name.endsWith('Capture');
    
  206.     const eventName = name.slice(2, useCapture ? name.length - 7 : undefined);
    
  207. 
    
  208.     const prevProps = getFiberCurrentPropsFromNode(node);
    
  209.     const prevValue = prevProps != null ? prevProps[name] : null;
    
  210.     if (typeof prevValue === 'function') {
    
  211.       node.removeEventListener(eventName, prevValue, useCapture);
    
  212.     }
    
  213.     if (typeof value === 'function') {
    
  214.       if (typeof prevValue !== 'function' && prevValue !== null) {
    
  215.         // If we previously assigned a non-function type into this node, then
    
  216.         // remove it when switching to event listener mode.
    
  217.         if (name in (node: any)) {
    
  218.           (node: any)[name] = null;
    
  219.         } else if (node.hasAttribute(name)) {
    
  220.           node.removeAttribute(name);
    
  221.         }
    
  222.       }
    
  223.       // $FlowFixMe[incompatible-cast] value can't be casted to EventListener.
    
  224.       node.addEventListener(eventName, (value: EventListener), useCapture);
    
  225.       return;
    
  226.     }
    
  227.   }
    
  228. 
    
  229.   if (name in (node: any)) {
    
  230.     (node: any)[name] = value;
    
  231.     return;
    
  232.   }
    
  233. 
    
  234.   if (value === true) {
    
  235.     node.setAttribute(name, '');
    
  236.     return;
    
  237.   }
    
  238. 
    
  239.   // From here, it's the same as any attribute
    
  240.   setValueForAttribute(node, name, value);
    
  241. }