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