1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {HostContext, HostContextDev} from './ReactFiberConfigDOM';1112import {HostContextNamespaceNone} from './ReactFiberConfigDOM';1314import {15 registrationNameDependencies,16 possibleRegistrationNames,17} from '../events/EventRegistry';1819import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion';20import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';21import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';2223import {24 getValueForAttribute,25 getValueForAttributeOnCustomComponent,26 setValueForPropertyOnCustomComponent,27 setValueForKnownAttribute,28 setValueForAttribute,29 setValueForNamespacedAttribute,30} from './DOMPropertyOperations';31import {32 validateInputProps,33 initInput,34 updateInput,35 restoreControlledInputState,36} from './ReactDOMInput';37import {validateOptionProps} from './ReactDOMOption';38import {39 validateSelectProps,40 initSelect,41 restoreControlledSelectState,42 updateSelect,43} from './ReactDOMSelect';44import {45 validateTextareaProps,46 initTextarea,47 updateTextarea,48 restoreControlledTextareaState,49} from './ReactDOMTextarea';50import {setSrcObject} from './ReactDOMSrcObject';51import {validateTextNesting} from './validateDOMNesting';52import setTextContent from './setTextContent';53import {54 createDangerousStringForStyles,55 setValueForStyles,56} from './CSSPropertyOperations';57import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces';58import isCustomElement from '../shared/isCustomElement';59import getAttributeAlias from '../shared/getAttributeAlias';60import possibleStandardNames from '../shared/possibleStandardNames';61import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';62import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';63import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';64import sanitizeURL from '../shared/sanitizeURL';6566import noop from 'shared/noop';6768import {trackHostMutation} from 'react-reconciler/src/ReactFiberMutationTracking';6970import {71 enableHydrationChangeEvent,72 enableScrollEndPolyfill,73 enableSrcObject,74 enableViewTransition,75 enableViewTransitionParentEnterExit,76} from 'shared/ReactFeatureFlags';77import {78 mediaEventTypes,79 listenToNonDelegatedEvent,80} from '../events/DOMPluginEventSystem';8182let didWarnControlledToUncontrolled = false;83let didWarnUncontrolledToControlled = false;84let didWarnFormActionType = false;85let didWarnFormActionName = false;86let didWarnFormActionTarget = false;87let didWarnFormActionMethod = false;88let didWarnForNewBooleanPropsWithEmptyValue: {[string]: boolean};89let didWarnPopoverTargetObject = false;90if (__DEV__) {91 didWarnForNewBooleanPropsWithEmptyValue = {};92}9394function validatePropertiesInDevelopment(type: string, props: any) {95 if (__DEV__) {96 validateARIAProperties(type, props);97 validateInputProperties(type, props);98 validateUnknownProperties(type, props, {99 registrationNameDependencies,100 possibleRegistrationNames,101 });102 if (103 props.contentEditable &&104 !props.suppressContentEditableWarning &&105 props.children != null106 ) {107 console.error(108 'A component is `contentEditable` and contains `children` managed by ' +109 'React. It is now your responsibility to guarantee that none of ' +110 'those nodes are unexpectedly modified or duplicated. This is ' +111 'probably not intentional.',112 );113 }114 }115}116117function validateFormActionInDevelopment(118 tag: string,119 key: string,120 value: mixed,121 props: any,122) {123 if (__DEV__) {124 if (value == null) {125 return;126 }127 if (tag === 'form') {128 if (key === 'formAction') {129 console.error(130 'You can only pass the formAction prop to <input> or <button>. Use the action prop on <form>.',131 );132 } else if (typeof value === 'function') {133 if (134 (props.encType != null || props.method != null) &&135 !didWarnFormActionMethod136 ) {137 didWarnFormActionMethod = true;138 console.error(139 'Cannot specify a encType or method for a form that specifies a ' +140 'function as the action. React provides those automatically. ' +141 'They will get overridden.',142 );143 }144 if (props.target != null && !didWarnFormActionTarget) {145 didWarnFormActionTarget = true;146 console.error(147 'Cannot specify a target for a form that specifies a function as the action. ' +148 'The function will always be executed in the same window.',149 );150 }151 }152 } else if (tag === 'input' || tag === 'button') {153 if (key === 'action') {154 console.error(155 'You can only pass the action prop to <form>. Use the formAction prop on <input> or <button>.',156 );157 } else if (158 tag === 'input' &&159 props.type !== 'submit' &&160 props.type !== 'image' &&161 !didWarnFormActionType162 ) {163 didWarnFormActionType = true;164 console.error(165 'An input can only specify a formAction along with type="submit" or type="image".',166 );167 } else if (168 tag === 'button' &&169 props.type != null &&170 props.type !== 'submit' &&171 !didWarnFormActionType172 ) {173 didWarnFormActionType = true;174 console.error(175 'A button can only specify a formAction along with type="submit" or no type.',176 );177 } else if (typeof value === 'function') {178 // Function form actions cannot control the form properties179 if (props.name != null && !didWarnFormActionName) {180 didWarnFormActionName = true;181 console.error(182 'Cannot specify a "name" prop for a button that specifies a function as a formAction. ' +183 'React needs it to encode which action should be invoked. It will get overridden.',184 );185 }186 if (187 (props.formEncType != null || props.formMethod != null) &&188 !didWarnFormActionMethod189 ) {190 didWarnFormActionMethod = true;191 console.error(192 'Cannot specify a formEncType or formMethod for a button that specifies a ' +193 'function as a formAction. React provides those automatically. They will get overridden.',194 );195 }196 if (props.formTarget != null && !didWarnFormActionTarget) {197 didWarnFormActionTarget = true;198 console.error(199 'Cannot specify a formTarget for a button that specifies a function as a formAction. ' +200 'The function will always be executed in the same window.',201 );202 }203 }204 } else {205 if (key === 'action') {206 console.error('You can only pass the action prop to <form>.');207 } else {208 console.error(209 'You can only pass the formAction prop to <input> or <button>.',210 );211 }212 }213 }214}215216function warnForPropDifference(217 propName: string,218 serverValue: mixed,219 clientValue: mixed,220 serverDifferences: {[propName: string]: mixed},221): void {222 if (__DEV__) {223 if (serverValue === clientValue) {224 return;225 }226 const normalizedClientValue =227 normalizeMarkupForTextOrAttribute(clientValue);228 const normalizedServerValue =229 normalizeMarkupForTextOrAttribute(serverValue);230 if (normalizedServerValue === normalizedClientValue) {231 return;232 }233234 serverDifferences[propName] = serverValue;235 }236}237238function hasViewTransition(htmlElement: HTMLElement): boolean {239 return !!(240 htmlElement.getAttribute('vt-share') ||241 htmlElement.getAttribute('vt-exit') ||242 htmlElement.getAttribute('vt-enter') ||243 htmlElement.getAttribute('vt-update') ||244 (enableViewTransitionParentEnterExit &&245 (htmlElement.getAttribute('vt-parent-enter') ||246 htmlElement.getAttribute('vt-parent-exit')))247 );248}249250function isExpectedViewTransitionName(htmlElement: HTMLElement): boolean {251 if (!hasViewTransition(htmlElement)) {252 // We didn't expect to see a view transition name applied.253 return false;254 }255 const expectedVtName = htmlElement.getAttribute('vt-name');256 const actualVtName: string = (htmlElement.style as any)[257 'view-transition-name'258 ];259 if (expectedVtName) {260 return expectedVtName === actualVtName;261 } else {262 // Auto-generated name.263 // TODO: If Fizz starts applying a prefix to this name, we need to consider that.264 return actualVtName.startsWith('_T_');265 }266}267268function warnForExtraAttributes(269 domElement: Element,270 attributeNames: Set<string>,271 serverDifferences: {[propName: string]: mixed},272) {273 if (__DEV__) {274 attributeNames.forEach(function (attributeName) {275 if (attributeName === 'style') {276 if (domElement.getAttribute(attributeName) === '') {277 // Skip empty style. It's fine.278 return;279 }280 const htmlElement = domElement as any as HTMLElement;281 const style = htmlElement.style;282 const isOnlyVTStyles =283 (style.length === 1 && style[0] === 'view-transition-name') ||284 (style.length === 2 &&285 style[0] === 'view-transition-class' &&286 style[1] === 'view-transition-name');287 if (isOnlyVTStyles && isExpectedViewTransitionName(htmlElement)) {288 // If the only extra style was the view-transition-name that we applied from the Fizz289 // runtime, then we should ignore it.290 } else {291 serverDifferences.style = getStylesObjectFromElement(domElement);292 }293 } else {294 serverDifferences[getPropNameFromAttributeName(attributeName)] =295 domElement.getAttribute(attributeName);296 }297 });298 }299}300301function warnForInvalidEventListener(registrationName: string, listener: any) {302 if (__DEV__) {303 if (listener === false) {304 console.error(305 'Expected `%s` listener to be a function, instead got `false`.\n\n' +306 'If you used to conditionally omit it with %s={condition && value}, ' +307 'pass %s={condition ? value : undefined} instead.',308 registrationName,309 registrationName,310 registrationName,311 );312 } else {313 console.error(314 'Expected `%s` listener to be a function, instead got a value of `%s` type.',315 registrationName,316 typeof listener,317 );318 }319 }320}321322// Parse the HTML and read it back to normalize the HTML string so that it323// can be used for comparison.324function normalizeHTML(parent: Element, html: string) {325 if (__DEV__) {326 // We could have created a separate document here to avoid327 // re-initializing custom elements if they exist. But this breaks328 // how <noscript> is being handled. So we use the same document.329 // See the discussion in https://github.com/react/react/pull/11157.330 const testElement =331 parent.namespaceURI === MATH_NAMESPACE ||332 parent.namespaceURI === SVG_NAMESPACE333 ? parent.ownerDocument.createElementNS(334 parent.namespaceURI as any,335 parent.tagName,336 )337 : parent.ownerDocument.createElement(parent.tagName);338 testElement.innerHTML = html;339 return testElement.innerHTML;340 }341}342343// HTML parsing normalizes CR and CRLF to LF.344// It also can turn \u0000 into \uFFFD inside attributes.345// https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream346// If we have a mismatch, it might be caused by that.347// We will still patch up in this case but not fire the warning.348const NORMALIZE_NEWLINES_REGEX = /\r\n?/g;349const NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g;350351function normalizeMarkupForTextOrAttribute(markup: mixed): string {352 if (__DEV__) {353 checkHtmlStringCoercion(markup);354 }355 const markupString =356 typeof markup === 'string' ? markup : '' + (markup as any);357 return markupString358 .replace(NORMALIZE_NEWLINES_REGEX, '\n')359 .replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, '');360}361362function checkForUnmatchedText(363 serverText: string,364 clientText: string | number | bigint,365) {366 const normalizedClientText = normalizeMarkupForTextOrAttribute(clientText);367 const normalizedServerText = normalizeMarkupForTextOrAttribute(serverText);368 if (normalizedServerText === normalizedClientText) {369 return true;370 }371 return false;372}373374export function trapClickOnNonInteractiveElement(node: HTMLElement) {375 // Mobile Safari does not fire properly bubble click events on376 // non-interactive elements, which means delegated click listeners do not377 // fire. The workaround for this bug involves attaching an empty click378 // listener on the target node.379 // https://www.quirksmode.org/blog/archives/2010/09/click_event_del.html380 // Just set it using the onclick property so that we don't have to manage any381 // bookkeeping for it. HostSingleton release clears the property only if it382 // still points to this noop.383 // TODO: Only do this for the relevant Safaris maybe?384 node.onclick = noop;385}386387export function clearClickListener(node: HTMLElement) {388 if (node.onclick === noop) {389 node.onclick = null;390 }391}392393const xlinkNamespace = 'http://www.w3.org/1999/xlink';394const xmlNamespace = 'http://www.w3.org/XML/1998/namespace';395396function setProp(397 domElement: Element,398 tag: string,399 key: string,400 value: mixed,401 props: any,402 prevValue: mixed,403): void {404 switch (key) {405 case 'children': {406 if (typeof value === 'string') {407 if (__DEV__) {408 validateTextNesting(value, tag, false);409 }410 // Avoid setting initial textContent when the text is empty. In IE11 setting411 // textContent on a <textarea> will cause the placeholder to not412 // show within the <textarea> until it has been focused and blurred again.413 // https://github.com/react/react/issues/6731#issuecomment-254874553414 const canSetTextContent =415 tag !== 'body' && (tag !== 'textarea' || value !== '');416 if (canSetTextContent) {417 setTextContent(domElement, value);418 }419 } else if (typeof value === 'number' || typeof value === 'bigint') {420 if (__DEV__) {421 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint422 validateTextNesting('' + value, tag, false);423 }424 const canSetTextContent = tag !== 'body';425 if (canSetTextContent) {426 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint427 setTextContent(domElement, '' + value);428 }429 } else {430 return;431 }432 break;433 }434 // These are very common props and therefore are in the beginning of the switch.435 // TODO: aria-label is a very common prop but allows booleans so is not like the others436 // but should ideally go in this list too.437 case 'className':438 setValueForKnownAttribute(domElement, 'class', value);439 break;440 case 'tabIndex':441 // This has to be case sensitive in SVG.442 setValueForKnownAttribute(domElement, 'tabindex', value);443 break;444 case 'dir':445 case 'role':446 case 'viewBox':447 case 'width':448 case 'height': {449 setValueForKnownAttribute(domElement, key, value);450 break;451 }452 case 'style': {453 setValueForStyles(domElement, value, prevValue);454 return;455 }456 // These attributes accept URLs. These must not allow javascript: URLS.457 case 'data':458 if (tag !== 'object') {459 setValueForKnownAttribute(domElement, 'data', value);460 break;461 }462 // fallthrough463 case 'src': {464 if (enableSrcObject && typeof value === 'object' && value !== null) {465 // Some tags support object sources like Blob, File, MediaSource and MediaStream.466 if (tag === 'img' || tag === 'video' || tag === 'audio') {467 try {468 setSrcObject(domElement, tag, value);469 break;470 } catch (x) {471 // If URL.createObjectURL() errors, it was probably some other object type472 // that should be toString:ed instead, so we just fall-through to the normal473 // path.474 }475 } else {476 if (__DEV__) {477 try {478 // This should always error.479 URL.revokeObjectURL(URL.createObjectURL(value as any));480 if (tag === 'source') {481 console.error(482 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +483 'Pass it directly to <img src>, <video src> or <audio src> instead.',484 );485 } else {486 console.error(487 'Passing Blob, MediaSource or MediaStream to <%s src> is not supported.',488 tag,489 );490 }491 } catch (x) {}492 }493 }494 }495 // Fallthrough496 }497 case 'href': {498 if (499 value === '' &&500 // <a href=""> is fine for "reload" links.501 !(tag === 'a' && key === 'href')502 ) {503 if (__DEV__) {504 if (key === 'src') {505 console.error(506 'An empty string ("") was passed to the %s attribute. ' +507 'This may cause the browser to download the whole page again over the network. ' +508 'To fix this, either do not render the element at all ' +509 'or pass null to %s instead of an empty string.',510 key,511 key,512 );513 } else {514 console.error(515 'An empty string ("") was passed to the %s attribute. ' +516 'To fix this, either do not render the element at all ' +517 'or pass null to %s instead of an empty string.',518 key,519 key,520 );521 }522 }523 domElement.removeAttribute(key);524 break;525 }526 if (527 value == null ||528 typeof value === 'function' ||529 typeof value === 'symbol' ||530 typeof value === 'boolean'531 ) {532 domElement.removeAttribute(key);533 break;534 }535 if (__DEV__) {536 checkAttributeStringCoercion(value, key);537 }538 const sanitizedValue = sanitizeURL(value) as any;539 domElement.setAttribute(key, sanitizedValue);540 break;541 }542 case 'action':543 case 'formAction': {544 // TODO: Consider moving these special cases to the form, input and button tags.545 if (__DEV__) {546 validateFormActionInDevelopment(tag, key, value, props);547 }548 if (typeof value === 'function') {549 // Set a javascript URL that doesn't do anything. We don't expect this to be invoked550 // because we'll preventDefault, but it can happen if a form is manually submitted or551 // if someone calls stopPropagation before React gets the event.552 // If CSP is used to block javascript: URLs that's fine too. It just won't show this553 // error message but the URL will be logged.554 domElement.setAttribute(555 key,556 // eslint-disable-next-line no-script-url557 "javascript:throw new Error('" +558 'A React form was unexpectedly submitted. If you called form.submit() manually, ' +559 "consider using form.requestSubmit() instead. If you\\'re trying to use " +560 'event.stopPropagation() in a submit event handler, consider also calling ' +561 'event.preventDefault().' +562 "')",563 );564 break;565 } else if (typeof prevValue === 'function') {566 // When we're switching off a Server Action that was originally hydrated.567 // The server control these fields during SSR that are now trailing.568 // The regular diffing doesn't apply since we compare against the previous props.569 // Instead, we need to force them to be set to whatever they should be now.570 // This would be a lot cleaner if we did this whole fork in the per-tag approach.571 if (key === 'formAction') {572 if (tag !== 'input') {573 // Setting the name here isn't completely safe for inputs if this is switching574 // to become a radio button. In that case we let the tag based override take575 // control.576 setProp(domElement, tag, 'name', props.name, props, null);577 }578 setProp(579 domElement,580 tag,581 'formEncType',582 props.formEncType,583 props,584 null,585 );586 setProp(domElement, tag, 'formMethod', props.formMethod, props, null);587 setProp(domElement, tag, 'formTarget', props.formTarget, props, null);588 } else {589 setProp(domElement, tag, 'encType', props.encType, props, null);590 setProp(domElement, tag, 'method', props.method, props, null);591 setProp(domElement, tag, 'target', props.target, props, null);592 }593 }594 if (595 value == null ||596 typeof value === 'symbol' ||597 typeof value === 'boolean'598 ) {599 domElement.removeAttribute(key);600 break;601 }602 if (__DEV__) {603 checkAttributeStringCoercion(value, key);604 }605 const sanitizedValue = sanitizeURL(value) as any;606 domElement.setAttribute(key, sanitizedValue);607 break;608 }609 case 'onClick': {610 // TODO: This cast may not be sound for SVG, MathML or custom elements.611 if (value != null) {612 if (__DEV__ && typeof value !== 'function') {613 warnForInvalidEventListener(key, value);614 }615 trapClickOnNonInteractiveElement(domElement as any as HTMLElement);616 }617 return;618 }619 case 'onScroll': {620 if (value != null) {621 if (__DEV__ && typeof value !== 'function') {622 warnForInvalidEventListener(key, value);623 }624 listenToNonDelegatedEvent('scroll', domElement);625 }626 return;627 }628 case 'onScrollEnd': {629 if (value != null) {630 if (__DEV__ && typeof value !== 'function') {631 warnForInvalidEventListener(key, value);632 }633 listenToNonDelegatedEvent('scrollend', domElement);634 if (enableScrollEndPolyfill) {635 // For use by the polyfill.636 listenToNonDelegatedEvent('scroll', domElement);637 }638 }639 return;640 }641 case 'dangerouslySetInnerHTML': {642 if (value != null) {643 if (typeof value !== 'object' || !('__html' in value)) {644 throw new Error(645 '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +646 'Please visit https://react.dev/link/dangerously-set-inner-html ' +647 'for more information.',648 );649 }650 const nextHtml: any = value.__html;651 if (nextHtml != null) {652 if (props.children != null) {653 throw new Error(654 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',655 );656 }657 const lastHtml: any =658 prevValue != null ? (prevValue as any).__html : undefined;659 if (lastHtml !== nextHtml) {660 domElement.innerHTML = nextHtml;661 }662 }663 }664 break;665 }666 // Note: `option.selected` is not updated if `select.multiple` is667 // disabled with `removeAttribute`. We have special logic for handling this.668 case 'multiple': {669 (domElement as any).multiple =670 value && typeof value !== 'function' && typeof value !== 'symbol';671 break;672 }673 case 'muted': {674 (domElement as any).muted =675 value && typeof value !== 'function' && typeof value !== 'symbol';676 break;677 }678 case 'suppressContentEditableWarning':679 case 'suppressHydrationWarning':680 case 'defaultValue': // Reserved681 case 'defaultChecked':682 case 'innerHTML':683 case 'ref': {684 // TODO: `ref` is pretty common, should we move it up?685 // Noop686 break;687 }688 case 'autoFocus': {689 // We polyfill it separately on the client during commit.690 // We could have excluded it in the property list instead of691 // adding a special case here, but then it wouldn't be emitted692 // on server rendering (but we *do* want to emit it in SSR).693 break;694 }695 case 'xlinkHref': {696 if (697 value == null ||698 typeof value === 'function' ||699 typeof value === 'boolean' ||700 typeof value === 'symbol'701 ) {702 domElement.removeAttribute('xlink:href');703 break;704 }705 if (__DEV__) {706 checkAttributeStringCoercion(value, key);707 }708 const sanitizedValue = sanitizeURL(value) as any;709 domElement.setAttributeNS(xlinkNamespace, 'xlink:href', sanitizedValue);710 break;711 }712 case 'contentEditable':713 case 'spellCheck':714 case 'draggable':715 case 'value':716 case 'autoReverse':717 case 'externalResourcesRequired':718 case 'focusable':719 case 'preserveAlpha': {720 // Booleanish String721 // These are "enumerated" attributes that accept "true" and "false".722 // In React, we let users pass `true` and `false` even though technically723 // these aren't boolean attributes (they are coerced to strings).724 // The SVG attributes are case-sensitive. Since the HTML attributes are725 // insensitive they also work even though we canonically use lower case.726 if (727 value != null &&728 typeof value !== 'function' &&729 typeof value !== 'symbol'730 ) {731 if (__DEV__) {732 checkAttributeStringCoercion(value, key);733 }734 domElement.setAttribute(key, value as any);735 } else {736 domElement.removeAttribute(key);737 }738 break;739 }740 // Boolean741 case 'inert': {742 if (__DEV__) {743 if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[key]) {744 didWarnForNewBooleanPropsWithEmptyValue[key] = true;745 console.error(746 'Received an empty string for a boolean attribute `%s`. ' +747 'This will treat the attribute as if it were false. ' +748 'Either pass `false` to silence this warning, or ' +749 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.',750 key,751 );752 }753 }754 }755 // Fallthrough for boolean props that don't have a warning for empty strings.756 case 'allowFullScreen':757 case 'async':758 case 'autoPlay':759 case 'controls':760 case 'credentialless':761 case 'default':762 case 'defer':763 case 'disabled':764 case 'disablePictureInPicture':765 case 'disableRemotePlayback':766 case 'formNoValidate':767 case 'hidden':768 case 'loop':769 case 'noModule':770 case 'noValidate':771 case 'open':772 case 'playsInline':773 case 'readOnly':774 case 'required':775 case 'reversed':776 case 'scoped':777 case 'seamless':778 case 'itemScope': {779 if (value && typeof value !== 'function' && typeof value !== 'symbol') {780 domElement.setAttribute(key, '');781 } else {782 domElement.removeAttribute(key);783 }784 break;785 }786 // Overloaded Boolean787 case 'capture':788 case 'download': {789 // An attribute that can be used as a flag as well as with a value.790 // When true, it should be present (set either to an empty string or its name).791 // When false, it should be omitted.792 // For any other value, should be present with that value.793 if (value === true) {794 domElement.setAttribute(key, '');795 } else if (796 value !== false &&797 value != null &&798 typeof value !== 'function' &&799 typeof value !== 'symbol'800 ) {801 if (__DEV__) {802 checkAttributeStringCoercion(value, key);803 }804 domElement.setAttribute(key, value as any);805 } else {806 domElement.removeAttribute(key);807 }808 break;809 }810 case 'cols':811 case 'rows':812 case 'size':813 case 'span': {814 // These are HTML attributes that must be positive numbers.815 if (816 value != null &&817 typeof value !== 'function' &&818 typeof value !== 'symbol' &&819 !isNaN(value) &&820 (value as any) >= 1821 ) {822 if (__DEV__) {823 checkAttributeStringCoercion(value, key);824 }825 domElement.setAttribute(key, value as any);826 } else {827 domElement.removeAttribute(key);828 }829 break;830 }831 case 'rowSpan':832 case 'start': {833 // These are HTML attributes that must be numbers.834 if (835 value != null &&836 typeof value !== 'function' &&837 typeof value !== 'symbol' &&838 !isNaN(value)839 ) {840 if (__DEV__) {841 checkAttributeStringCoercion(value, key);842 }843 domElement.setAttribute(key, value as any);844 } else {845 domElement.removeAttribute(key);846 }847 break;848 }849 case 'popover':850 listenToNonDelegatedEvent('beforetoggle', domElement);851 listenToNonDelegatedEvent('toggle', domElement);852 setValueForAttribute(domElement, 'popover', value);853 break;854 case 'xlinkActuate':855 setValueForNamespacedAttribute(856 domElement,857 xlinkNamespace,858 'xlink:actuate',859 value,860 );861 break;862 case 'xlinkArcrole':863 setValueForNamespacedAttribute(864 domElement,865 xlinkNamespace,866 'xlink:arcrole',867 value,868 );869 break;870 case 'xlinkRole':871 setValueForNamespacedAttribute(872 domElement,873 xlinkNamespace,874 'xlink:role',875 value,876 );877 break;878 case 'xlinkShow':879 setValueForNamespacedAttribute(880 domElement,881 xlinkNamespace,882 'xlink:show',883 value,884 );885 break;886 case 'xlinkTitle':887 setValueForNamespacedAttribute(888 domElement,889 xlinkNamespace,890 'xlink:title',891 value,892 );893 break;894 case 'xlinkType':895 setValueForNamespacedAttribute(896 domElement,897 xlinkNamespace,898 'xlink:type',899 value,900 );901 break;902 case 'xmlBase':903 setValueForNamespacedAttribute(904 domElement,905 xmlNamespace,906 'xml:base',907 value,908 );909 break;910 case 'xmlLang':911 setValueForNamespacedAttribute(912 domElement,913 xmlNamespace,914 'xml:lang',915 value,916 );917 break;918 case 'xmlSpace':919 setValueForNamespacedAttribute(920 domElement,921 xmlNamespace,922 'xml:space',923 value,924 );925 break;926 // Properties that should not be allowed on custom elements.927 case 'is': {928 if (__DEV__) {929 if (prevValue != null) {930 console.error(931 'Cannot update the "is" prop after it has been initialized.',932 );933 }934 }935 // TODO: We shouldn't actually set this attribute, because we've already936 // passed it to createElement. We don't also need the attribute.937 // However, our tests currently query for it so it's plausible someone938 // else does too so it's break.939 setValueForAttribute(domElement, 'is', value);940 break;941 }942 case 'innerText':943 case 'textContent':944 return;945 case 'popoverTarget':946 if (__DEV__) {947 if (948 !didWarnPopoverTargetObject &&949 value != null &&950 typeof value === 'object'951 ) {952 didWarnPopoverTargetObject = true;953 console.error(954 'The `popoverTarget` prop expects the ID of an Element as a string. Received %s instead.',955 value,956 );957 }958 }959 // Fall through960 default: {961 if (962 key.length > 2 &&963 (key[0] === 'o' || key[0] === 'O') &&964 (key[1] === 'n' || key[1] === 'N')965 ) {966 if (967 __DEV__ &&968 registrationNameDependencies.hasOwnProperty(key) &&969 value != null &&970 typeof value !== 'function'971 ) {972 warnForInvalidEventListener(key, value);973 }974 // Updating events doesn't affect the visuals.975 return;976 } else {977 const attributeName = getAttributeAlias(key);978 setValueForAttribute(domElement, attributeName, value);979 }980 }981 }982 // To avoid marking things as host mutations we do early returns above.983 trackHostMutation();984}985986function setPropOnCustomElement(987 domElement: Element,988 tag: string,989 key: string,990 value: mixed,991 props: any,992 prevValue: mixed,993): void {994 switch (key) {995 case 'style': {996 setValueForStyles(domElement, value, prevValue);997 return;998 }999 case 'dangerouslySetInnerHTML': {1000 if (value != null) {1001 if (typeof value !== 'object' || !('__html' in value)) {1002 throw new Error(1003 '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +1004 'Please visit https://react.dev/link/dangerously-set-inner-html ' +1005 'for more information.',1006 );1007 }1008 const nextHtml: any = value.__html;1009 if (nextHtml != null) {1010 if (props.children != null) {1011 throw new Error(1012 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',1013 );1014 }1015 const lastHtml: any =1016 prevValue != null ? (prevValue as any).__html : undefined;1017 if (lastHtml !== nextHtml) {1018 domElement.innerHTML = nextHtml;1019 }1020 }1021 }1022 break;1023 }1024 case 'children': {1025 if (typeof value === 'string') {1026 setTextContent(domElement, value);1027 } else if (typeof value === 'number' || typeof value === 'bigint') {1028 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint1029 setTextContent(domElement, '' + value);1030 } else {1031 return;1032 }1033 break;1034 }1035 case 'onScroll': {1036 if (value != null) {1037 if (__DEV__ && typeof value !== 'function') {1038 warnForInvalidEventListener(key, value);1039 }1040 listenToNonDelegatedEvent('scroll', domElement);1041 }1042 return;1043 }1044 case 'onScrollEnd': {1045 if (value != null) {1046 if (__DEV__ && typeof value !== 'function') {1047 warnForInvalidEventListener(key, value);1048 }1049 listenToNonDelegatedEvent('scrollend', domElement);1050 if (enableScrollEndPolyfill) {1051 // For use by the polyfill.1052 listenToNonDelegatedEvent('scroll', domElement);1053 }1054 }1055 return;1056 }1057 case 'onClick': {1058 // TODO: This cast may not be sound for SVG, MathML or custom elements.1059 if (value != null) {1060 if (__DEV__ && typeof value !== 'function') {1061 warnForInvalidEventListener(key, value);1062 }1063 trapClickOnNonInteractiveElement(domElement as any as HTMLElement);1064 }1065 return;1066 }1067 case 'suppressContentEditableWarning':1068 case 'suppressHydrationWarning':1069 case 'innerHTML':1070 case 'ref': {1071 // Noop1072 return;1073 }1074 case 'innerText': // Properties1075 case 'textContent':1076 return;1077 // Fall through1078 default: {1079 if (registrationNameDependencies.hasOwnProperty(key)) {1080 if (__DEV__ && value != null && typeof value !== 'function') {1081 warnForInvalidEventListener(key, value);1082 }1083 return;1084 } else {1085 setValueForPropertyOnCustomComponent(domElement, key, value);1086 // We track mutations inside this call.1087 return;1088 }1089 }1090 }1091 // To avoid marking things as host mutations we do early returns above.1092 trackHostMutation();1093}10941095export function setInitialProperties(1096 domElement: Element,1097 tag: string,1098 props: Object,1099): void {1100 if (__DEV__) {1101 validatePropertiesInDevelopment(tag, props);1102 }11031104 // TODO: Make sure that we check isMounted before firing any of these events.11051106 switch (tag) {1107 case 'div':1108 case 'span':1109 case 'svg':1110 case 'path':1111 case 'a':1112 case 'g':1113 case 'p':1114 case 'li': {1115 // Fast track the most common tag types1116 break;1117 }1118 // img tags previously were implemented as void elements with non delegated events however Safari (and possibly Firefox)1119 // begin fetching the image as soon as the `src` or `srcSet` property is set and if we set these before other properties1120 // that can modify the request (such as crossorigin) or the resource fetch (such as sizes) then the browser will load1121 // the wrong thing or load more than one thing. This implementation ensures src and srcSet are set on the instance last1122 case 'img': {1123 listenToNonDelegatedEvent('error', domElement);1124 listenToNonDelegatedEvent('load', domElement);1125 // Mostly a port of Void Element logic with special casing to ensure srcset and src are set last1126 let hasSrc = false;1127 let hasSrcSet = false;1128 for (const propKey in props) {1129 if (!props.hasOwnProperty(propKey)) {1130 continue;1131 }1132 const propValue = props[propKey];1133 if (propValue == null) {1134 continue;1135 }1136 switch (propKey) {1137 case 'src':1138 hasSrc = true;1139 break;1140 case 'srcSet':1141 hasSrcSet = true;1142 break;1143 case 'children':1144 case 'dangerouslySetInnerHTML': {1145 // TODO: Can we make this a DEV warning to avoid this deny list?1146 throw new Error(1147 `${tag} is a void element tag and must neither have \`children\` nor ` +1148 'use `dangerouslySetInnerHTML`.',1149 );1150 }1151 // defaultChecked and defaultValue are ignored by setProp1152 default: {1153 setProp(domElement, tag, propKey, propValue, props, null);1154 }1155 }1156 }1157 if (hasSrcSet) {1158 setProp(domElement, tag, 'srcSet', props.srcSet, props, null);1159 }1160 if (hasSrc) {1161 setProp(domElement, tag, 'src', props.src, props, null);1162 }1163 return;1164 }1165 case 'input': {1166 if (__DEV__) {1167 checkControlledValueProps('input', props);1168 }1169 // We listen to this event in case to ensure emulated bubble1170 // listeners still fire for the invalid event.1171 listenToNonDelegatedEvent('invalid', domElement);11721173 let name = null;1174 let type = null;1175 let value = null;1176 let defaultValue = null;1177 let checked = null;1178 let defaultChecked = null;1179 for (const propKey in props) {1180 if (!props.hasOwnProperty(propKey)) {1181 continue;1182 }1183 const propValue = props[propKey];1184 if (propValue == null) {1185 continue;1186 }1187 switch (propKey) {1188 case 'name': {1189 name = propValue;1190 break;1191 }1192 case 'type': {1193 type = propValue;1194 break;1195 }1196 case 'checked': {1197 checked = propValue;1198 break;1199 }1200 case 'defaultChecked': {1201 defaultChecked = propValue;1202 break;1203 }1204 case 'value': {1205 value = propValue;1206 break;1207 }1208 case 'defaultValue': {1209 defaultValue = propValue;1210 break;1211 }1212 case 'children':1213 case 'dangerouslySetInnerHTML': {1214 if (propValue != null) {1215 throw new Error(1216 `${tag} is a void element tag and must neither have \`children\` nor ` +1217 'use `dangerouslySetInnerHTML`.',1218 );1219 }1220 break;1221 }1222 default: {1223 setProp(domElement, tag, propKey, propValue, props, null);1224 }1225 }1226 }1227 // TODO: Make sure we check if this is still unmounted or do any clean1228 // up necessary since we never stop tracking anymore.1229 validateInputProps(domElement, props);1230 initInput(1231 domElement,1232 value,1233 defaultValue,1234 checked,1235 defaultChecked,1236 type,1237 name,1238 false,1239 );1240 return;1241 }1242 case 'select': {1243 if (__DEV__) {1244 checkControlledValueProps('select', props);1245 }1246 // We listen to this event in case to ensure emulated bubble1247 // listeners still fire for the invalid event.1248 listenToNonDelegatedEvent('invalid', domElement);1249 let value = null;1250 let defaultValue = null;1251 let multiple = null;1252 for (const propKey in props) {1253 if (!props.hasOwnProperty(propKey)) {1254 continue;1255 }1256 const propValue = props[propKey];1257 if (propValue == null) {1258 continue;1259 }1260 switch (propKey) {1261 case 'value': {1262 value = propValue;1263 // This is handled by initSelect below.1264 break;1265 }1266 case 'defaultValue': {1267 defaultValue = propValue;1268 // This is handled by initSelect below.1269 break;1270 }1271 case 'multiple': {1272 multiple = propValue;1273 // TODO: We don't actually have to fall through here because we set it1274 // in initSelect anyway. We can remove the special case in setProp.1275 }1276 // Fallthrough1277 default: {1278 setProp(domElement, tag, propKey, propValue, props, null);1279 }1280 }1281 }1282 validateSelectProps(domElement, props);1283 initSelect(domElement, value, defaultValue, multiple);1284 return;1285 }1286 case 'textarea': {1287 if (__DEV__) {1288 checkControlledValueProps('textarea', props);1289 }1290 // We listen to this event in case to ensure emulated bubble1291 // listeners still fire for the invalid event.1292 listenToNonDelegatedEvent('invalid', domElement);1293 let value = null;1294 let defaultValue = null;1295 let children = null;1296 for (const propKey in props) {1297 if (!props.hasOwnProperty(propKey)) {1298 continue;1299 }1300 const propValue = props[propKey];1301 if (propValue == null) {1302 continue;1303 }1304 switch (propKey) {1305 case 'value': {1306 value = propValue;1307 // This is handled by initTextarea below.1308 break;1309 }1310 case 'defaultValue': {1311 defaultValue = propValue;1312 break;1313 }1314 case 'children': {1315 children = propValue;1316 // Handled by initTextarea above.1317 break;1318 }1319 case 'dangerouslySetInnerHTML': {1320 if (propValue != null) {1321 // TODO: Do we really need a special error message for this. It's also pretty blunt.1322 throw new Error(1323 '`dangerouslySetInnerHTML` does not make sense on <textarea>.',1324 );1325 }1326 break;1327 }1328 default: {1329 setProp(domElement, tag, propKey, propValue, props, null);1330 }1331 }1332 }1333 // TODO: Make sure we check if this is still unmounted or do any clean1334 // up necessary since we never stop tracking anymore.1335 validateTextareaProps(domElement, props);1336 initTextarea(domElement, value, defaultValue, children);1337 return;1338 }1339 case 'option': {1340 validateOptionProps(domElement, props);1341 for (const propKey in props) {1342 if (!props.hasOwnProperty(propKey)) {1343 continue;1344 }1345 const propValue = props[propKey];1346 if (propValue == null) {1347 continue;1348 }1349 switch (propKey) {1350 case 'selected': {1351 // TODO: Remove support for selected on option.1352 (domElement as any).selected =1353 propValue &&1354 typeof propValue !== 'function' &&1355 typeof propValue !== 'symbol';1356 break;1357 }1358 default: {1359 setProp(domElement, tag, propKey, propValue, props, null);1360 }1361 }1362 }1363 return;1364 }1365 case 'dialog': {1366 listenToNonDelegatedEvent('beforetoggle', domElement);1367 listenToNonDelegatedEvent('toggle', domElement);1368 listenToNonDelegatedEvent('cancel', domElement);1369 listenToNonDelegatedEvent('close', domElement);1370 break;1371 }1372 case 'iframe':1373 case 'object': {1374 // We listen to this event in case to ensure emulated bubble1375 // listeners still fire for the load event.1376 listenToNonDelegatedEvent('load', domElement);1377 break;1378 }1379 case 'video':1380 case 'audio': {1381 // We listen to these events in case to ensure emulated bubble1382 // listeners still fire for all the media events.1383 for (let i = 0; i < mediaEventTypes.length; i++) {1384 listenToNonDelegatedEvent(mediaEventTypes[i], domElement);1385 }1386 break;1387 }1388 case 'image': {1389 // We listen to these events in case to ensure emulated bubble1390 // listeners still fire for error and load events.1391 listenToNonDelegatedEvent('error', domElement);1392 listenToNonDelegatedEvent('load', domElement);1393 break;1394 }1395 case 'details': {1396 // We listen to this event in case to ensure emulated bubble1397 // listeners still fire for the toggle event.1398 listenToNonDelegatedEvent('toggle', domElement);1399 break;1400 }1401 case 'embed':1402 case 'source':1403 case 'link': {1404 // These are void elements that also need delegated events.1405 listenToNonDelegatedEvent('error', domElement);1406 listenToNonDelegatedEvent('load', domElement);1407 // We fallthrough to the return of the void elements1408 }1409 case 'area':1410 case 'base':1411 case 'br':1412 case 'col':1413 case 'hr':1414 case 'keygen':1415 case 'meta':1416 case 'param':1417 case 'track':1418 case 'wbr':1419 case 'menuitem': {1420 // Void elements1421 for (const propKey in props) {1422 if (!props.hasOwnProperty(propKey)) {1423 continue;1424 }1425 const propValue = props[propKey];1426 if (propValue == null) {1427 continue;1428 }1429 switch (propKey) {1430 case 'children':1431 case 'dangerouslySetInnerHTML': {1432 // TODO: Can we make this a DEV warning to avoid this deny list?1433 throw new Error(1434 `${tag} is a void element tag and must neither have \`children\` nor ` +1435 'use `dangerouslySetInnerHTML`.',1436 );1437 }1438 // defaultChecked and defaultValue are ignored by setProp1439 default: {1440 setProp(domElement, tag, propKey, propValue, props, null);1441 }1442 }1443 }1444 return;1445 }1446 default: {1447 if (isCustomElement(tag, props)) {1448 for (const propKey in props) {1449 if (!props.hasOwnProperty(propKey)) {1450 continue;1451 }1452 const propValue = props[propKey];1453 if (propValue === undefined) {1454 continue;1455 }1456 setPropOnCustomElement(1457 domElement,1458 tag,1459 propKey,1460 propValue,1461 props,1462 undefined,1463 );1464 }1465 return;1466 }1467 }1468 }14691470 for (const propKey in props) {1471 if (!props.hasOwnProperty(propKey)) {1472 continue;1473 }1474 const propValue = props[propKey];1475 if (propValue == null) {1476 continue;1477 }1478 setProp(domElement, tag, propKey, propValue, props, null);1479 }1480}14811482export type SingletonType = 'html' | 'head' | 'body';14831484const emptyProps = {};14851486export function clearSingletonProperties(1487 domElement: Element,1488 tag: SingletonType,1489 props: Object,1490): void {1491 // This is equivalent to updating to empty props for tags without1492 // tag-specific update logic. Host singletons are limited to html, head, and1493 // body, so they always use this generic path.1494 for (const propKey in props) {1495 const propValue = props[propKey];1496 if (props.hasOwnProperty(propKey) && propValue != null) {1497 setProp(domElement, tag, propKey, null, emptyProps, propValue);1498 }1499 }1500}15011502export function updateProperties(1503 domElement: Element,1504 tag: string,1505 lastProps: Object,1506 nextProps: Object,1507): void {1508 if (__DEV__) {1509 validatePropertiesInDevelopment(tag, nextProps);1510 }15111512 switch (tag) {1513 case 'div':1514 case 'span':1515 case 'svg':1516 case 'path':1517 case 'a':1518 case 'g':1519 case 'p':1520 case 'li': {1521 // Fast track the most common tag types1522 break;1523 }1524 case 'input': {1525 let name = null;1526 let type = null;1527 let value = null;1528 let defaultValue = null;1529 let lastDefaultValue = null;1530 let checked = null;1531 let defaultChecked = null;1532 for (const propKey in lastProps) {1533 const lastProp = lastProps[propKey];1534 if (lastProps.hasOwnProperty(propKey) && lastProp != null) {1535 switch (propKey) {1536 case 'checked': {1537 break;1538 }1539 case 'value': {1540 // This is handled by updateWrapper below.1541 break;1542 }1543 case 'defaultValue': {1544 lastDefaultValue = lastProp;1545 }1546 // defaultChecked and defaultValue are ignored by setProp1547 // Fallthrough1548 default: {1549 if (!nextProps.hasOwnProperty(propKey))1550 setProp(domElement, tag, propKey, null, nextProps, lastProp);1551 }1552 }1553 }1554 }1555 for (const propKey in nextProps) {1556 const nextProp = nextProps[propKey];1557 const lastProp = lastProps[propKey];1558 if (1559 nextProps.hasOwnProperty(propKey) &&1560 (nextProp != null || lastProp != null)1561 ) {1562 switch (propKey) {1563 case 'type': {1564 if (nextProp !== lastProp) {1565 trackHostMutation();1566 }1567 type = nextProp;1568 break;1569 }1570 case 'name': {1571 if (nextProp !== lastProp) {1572 trackHostMutation();1573 }1574 name = nextProp;1575 break;1576 }1577 case 'checked': {1578 if (nextProp !== lastProp) {1579 trackHostMutation();1580 }1581 checked = nextProp;1582 break;1583 }1584 case 'defaultChecked': {1585 if (nextProp !== lastProp) {1586 trackHostMutation();1587 }1588 defaultChecked = nextProp;1589 break;1590 }1591 case 'value': {1592 if (nextProp !== lastProp) {1593 trackHostMutation();1594 }1595 value = nextProp;1596 break;1597 }1598 case 'defaultValue': {1599 if (nextProp !== lastProp) {1600 trackHostMutation();1601 }1602 defaultValue = nextProp;1603 break;1604 }1605 case 'children':1606 case 'dangerouslySetInnerHTML': {1607 if (nextProp != null) {1608 throw new Error(1609 `${tag} is a void element tag and must neither have \`children\` nor ` +1610 'use `dangerouslySetInnerHTML`.',1611 );1612 }1613 break;1614 }1615 default: {1616 if (nextProp !== lastProp)1617 setProp(1618 domElement,1619 tag,1620 propKey,1621 nextProp,1622 nextProps,1623 lastProp,1624 );1625 }1626 }1627 }1628 }16291630 if (__DEV__) {1631 const wasControlled =1632 lastProps.type === 'checkbox' || lastProps.type === 'radio'1633 ? lastProps.checked != null1634 : lastProps.value != null;1635 const isControlled =1636 nextProps.type === 'checkbox' || nextProps.type === 'radio'1637 ? nextProps.checked != null1638 : nextProps.value != null;16391640 if (1641 !wasControlled &&1642 isControlled &&1643 !didWarnUncontrolledToControlled1644 ) {1645 console.error(1646 'A component is changing an uncontrolled input to be controlled. ' +1647 'This is likely caused by the value changing from undefined to ' +1648 'a defined value, which should not happen. ' +1649 'Decide between using a controlled or uncontrolled input ' +1650 'element for the lifetime of the component. More info: https://react.dev/link/controlled-components',1651 );1652 didWarnUncontrolledToControlled = true;1653 }1654 if (1655 wasControlled &&1656 !isControlled &&1657 !didWarnControlledToUncontrolled1658 ) {1659 console.error(1660 'A component is changing a controlled input to be uncontrolled. ' +1661 'This is likely caused by the value changing from a defined to ' +1662 'undefined, which should not happen. ' +1663 'Decide between using a controlled or uncontrolled input ' +1664 'element for the lifetime of the component. More info: https://react.dev/link/controlled-components',1665 );1666 didWarnControlledToUncontrolled = true;1667 }1668 }16691670 // Update the wrapper around inputs *after* updating props. This has to1671 // happen after updating the rest of props. Otherwise HTML5 input validations1672 // raise warnings and prevent the new value from being assigned.1673 updateInput(1674 domElement,1675 value,1676 defaultValue,1677 lastDefaultValue,1678 checked,1679 defaultChecked,1680 type,1681 name,1682 );1683 return;1684 }1685 case 'select': {1686 let value = null;1687 let defaultValue = null;1688 let multiple = null;1689 let wasMultiple = null;1690 for (const propKey in lastProps) {1691 const lastProp = lastProps[propKey];1692 if (lastProps.hasOwnProperty(propKey) && lastProp != null) {1693 switch (propKey) {1694 case 'value': {1695 // This is handled by updateWrapper below.1696 break;1697 }1698 // defaultValue are ignored by setProp1699 case 'multiple': {1700 wasMultiple = lastProp;1701 // TODO: Move special case in here from setProp.1702 }1703 // Fallthrough1704 default: {1705 if (!nextProps.hasOwnProperty(propKey)) {1706 setProp(domElement, tag, propKey, null, nextProps, lastProp);1707 }1708 }1709 }1710 }1711 }1712 for (const propKey in nextProps) {1713 const nextProp = nextProps[propKey];1714 const lastProp = lastProps[propKey];1715 if (1716 nextProps.hasOwnProperty(propKey) &&1717 (nextProp != null || lastProp != null)1718 ) {1719 switch (propKey) {1720 case 'value': {1721 if (nextProp !== lastProp) {1722 trackHostMutation();1723 }1724 value = nextProp;1725 // This is handled by updateSelect below.1726 break;1727 }1728 case 'defaultValue': {1729 if (nextProp !== lastProp) {1730 trackHostMutation();1731 }1732 defaultValue = nextProp;1733 break;1734 }1735 case 'multiple': {1736 if (nextProp !== lastProp) {1737 trackHostMutation();1738 }1739 multiple = nextProp;1740 // TODO: Just move the special case in here from setProp.1741 }1742 // Fallthrough1743 default: {1744 if (nextProp !== lastProp)1745 setProp(1746 domElement,1747 tag,1748 propKey,1749 nextProp,1750 nextProps,1751 lastProp,1752 );1753 }1754 }1755 }1756 }1757 // <select> value update needs to occur after <option> children1758 // reconciliation1759 updateSelect(domElement, value, defaultValue, multiple, wasMultiple);1760 return;1761 }1762 case 'textarea': {1763 let value = null;1764 let defaultValue = null;1765 for (const propKey in lastProps) {1766 const lastProp = lastProps[propKey];1767 if (1768 lastProps.hasOwnProperty(propKey) &&1769 lastProp != null &&1770 !nextProps.hasOwnProperty(propKey)1771 ) {1772 switch (propKey) {1773 case 'value': {1774 // This is handled by updateTextarea below.1775 break;1776 }1777 case 'children': {1778 // TODO: This doesn't actually do anything if it updates.1779 break;1780 }1781 // defaultValue is ignored by setProp1782 default: {1783 setProp(domElement, tag, propKey, null, nextProps, lastProp);1784 }1785 }1786 }1787 }1788 for (const propKey in nextProps) {1789 const nextProp = nextProps[propKey];1790 const lastProp = lastProps[propKey];1791 if (1792 nextProps.hasOwnProperty(propKey) &&1793 (nextProp != null || lastProp != null)1794 ) {1795 switch (propKey) {1796 case 'value': {1797 if (nextProp !== lastProp) {1798 trackHostMutation();1799 }1800 value = nextProp;1801 // This is handled by updateTextarea below.1802 break;1803 }1804 case 'defaultValue': {1805 if (nextProp !== lastProp) {1806 trackHostMutation();1807 }1808 defaultValue = nextProp;1809 break;1810 }1811 case 'children': {1812 // TODO: This doesn't actually do anything if it updates.1813 break;1814 }1815 case 'dangerouslySetInnerHTML': {1816 if (nextProp != null) {1817 // TODO: Do we really need a special error message for this. It's also pretty blunt.1818 throw new Error(1819 '`dangerouslySetInnerHTML` does not make sense on <textarea>.',1820 );1821 }1822 break;1823 }1824 default: {1825 if (nextProp !== lastProp)1826 setProp(1827 domElement,1828 tag,1829 propKey,1830 nextProp,1831 nextProps,1832 lastProp,1833 );1834 }1835 }1836 }1837 }1838 updateTextarea(domElement, value, defaultValue);1839 return;1840 }1841 case 'option': {1842 for (const propKey in lastProps) {1843 const lastProp = lastProps[propKey];1844 if (1845 lastProps.hasOwnProperty(propKey) &&1846 lastProp != null &&1847 !nextProps.hasOwnProperty(propKey)1848 ) {1849 switch (propKey) {1850 case 'selected': {1851 // TODO: Remove support for selected on option.1852 (domElement as any).selected = false;1853 break;1854 }1855 default: {1856 setProp(domElement, tag, propKey, null, nextProps, lastProp);1857 }1858 }1859 }1860 }1861 for (const propKey in nextProps) {1862 const nextProp = nextProps[propKey];1863 const lastProp = lastProps[propKey];1864 if (1865 nextProps.hasOwnProperty(propKey) &&1866 nextProp !== lastProp &&1867 (nextProp != null || lastProp != null)1868 ) {1869 switch (propKey) {1870 case 'selected': {1871 if (nextProp !== lastProp) {1872 trackHostMutation();1873 }1874 // TODO: Remove support for selected on option.1875 (domElement as any).selected =1876 nextProp &&1877 typeof nextProp !== 'function' &&1878 typeof nextProp !== 'symbol';1879 break;1880 }1881 default: {1882 setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);1883 }1884 }1885 }1886 }1887 return;1888 }1889 case 'img':1890 case 'link':1891 case 'area':1892 case 'base':1893 case 'br':1894 case 'col':1895 case 'embed':1896 case 'hr':1897 case 'keygen':1898 case 'meta':1899 case 'param':1900 case 'source':1901 case 'track':1902 case 'wbr':1903 case 'menuitem': {1904 // Void elements1905 for (const propKey in lastProps) {1906 const lastProp = lastProps[propKey];1907 if (1908 lastProps.hasOwnProperty(propKey) &&1909 lastProp != null &&1910 !nextProps.hasOwnProperty(propKey)1911 ) {1912 setProp(domElement, tag, propKey, null, nextProps, lastProp);1913 }1914 }1915 for (const propKey in nextProps) {1916 const nextProp = nextProps[propKey];1917 const lastProp = lastProps[propKey];1918 if (1919 nextProps.hasOwnProperty(propKey) &&1920 nextProp !== lastProp &&1921 (nextProp != null || lastProp != null)1922 ) {1923 switch (propKey) {1924 case 'children':1925 case 'dangerouslySetInnerHTML': {1926 if (nextProp != null) {1927 // TODO: Can we make this a DEV warning to avoid this deny list?1928 throw new Error(1929 `${tag} is a void element tag and must neither have \`children\` nor ` +1930 'use `dangerouslySetInnerHTML`.',1931 );1932 }1933 break;1934 }1935 // defaultChecked and defaultValue are ignored by setProp1936 default: {1937 setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);1938 }1939 }1940 }1941 }1942 return;1943 }1944 default: {1945 if (isCustomElement(tag, nextProps)) {1946 for (const propKey in lastProps) {1947 const lastProp = lastProps[propKey];1948 if (1949 lastProps.hasOwnProperty(propKey) &&1950 lastProp !== undefined &&1951 !nextProps.hasOwnProperty(propKey)1952 ) {1953 setPropOnCustomElement(1954 domElement,1955 tag,1956 propKey,1957 undefined,1958 nextProps,1959 lastProp,1960 );1961 }1962 }1963 for (const propKey in nextProps) {1964 const nextProp = nextProps[propKey];1965 const lastProp = lastProps[propKey];1966 if (1967 nextProps.hasOwnProperty(propKey) &&1968 nextProp !== lastProp &&1969 (nextProp !== undefined || lastProp !== undefined)1970 ) {1971 setPropOnCustomElement(1972 domElement,1973 tag,1974 propKey,1975 nextProp,1976 nextProps,1977 lastProp,1978 );1979 }1980 }1981 return;1982 }1983 }1984 }19851986 for (const propKey in lastProps) {1987 const lastProp = lastProps[propKey];1988 if (1989 lastProps.hasOwnProperty(propKey) &&1990 lastProp != null &&1991 !nextProps.hasOwnProperty(propKey)1992 ) {1993 setProp(domElement, tag, propKey, null, nextProps, lastProp);1994 }1995 }1996 for (const propKey in nextProps) {1997 const nextProp = nextProps[propKey];1998 const lastProp = lastProps[propKey];1999 if (2000 nextProps.hasOwnProperty(propKey) &&
Findings
✓ No findings reported for this file.