import type {HostContext, HostContextDev} from './ReactFiberConfigDOM';
import {HostContextNamespaceNone} from './ReactFiberConfigDOM';
import {
registrationNameDependencies,
possibleRegistrationNames,
} from '../events/EventRegistry';
import {checkHtmlStringCoercion} from 'shared/CheckStringCoercion';
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';
import {
getValueForAttribute,
getValueForAttributeOnCustomComponent,
setValueForPropertyOnCustomComponent,
setValueForKnownAttribute,
setValueForAttribute,
setValueForNamespacedAttribute,
} from './DOMPropertyOperations';
import {
validateInputProps,
initInput,
updateInput,
restoreControlledInputState,
} from './ReactDOMInput';
import {validateOptionProps} from './ReactDOMOption';
import {
validateSelectProps,
initSelect,
restoreControlledSelectState,
updateSelect,
} from './ReactDOMSelect';
import {
validateTextareaProps,
initTextarea,
updateTextarea,
restoreControlledTextareaState,
} from './ReactDOMTextarea';
import {setSrcObject} from './ReactDOMSrcObject';
import {validateTextNesting} from './validateDOMNesting';
import setTextContent from './setTextContent';
import {
createDangerousStringForStyles,
setValueForStyles,
} from './CSSPropertyOperations';
import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces';
import isCustomElement from '../shared/isCustomElement';
import getAttributeAlias from '../shared/getAttributeAlias';
import possibleStandardNames from '../shared/possibleStandardNames';
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
import sanitizeURL from '../shared/sanitizeURL';
import noop from 'shared/noop';
import {trackHostMutation} from 'react-reconciler/src/ReactFiberMutationTracking';
import {
enableHydrationChangeEvent,
enableScrollEndPolyfill,
enableSrcObject,
enableTrustedTypesIntegration,
enableViewTransition,
} from 'shared/ReactFeatureFlags';
import {
mediaEventTypes,
listenToNonDelegatedEvent,
} from '../events/DOMPluginEventSystem';
let didWarnControlledToUncontrolled = false;
let didWarnUncontrolledToControlled = false;
let didWarnFormActionType = false;
let didWarnFormActionName = false;
let didWarnFormActionTarget = false;
let didWarnFormActionMethod = false;
let didWarnForNewBooleanPropsWithEmptyValue: {[string]: boolean};
let didWarnPopoverTargetObject = false;
if (__DEV__) {
didWarnForNewBooleanPropsWithEmptyValue = {};
}
function validatePropertiesInDevelopment(type: string, props: any) {
if (__DEV__) {
validateARIAProperties(type, props);
validateInputProperties(type, props);
validateUnknownProperties(type, props, {
registrationNameDependencies,
possibleRegistrationNames,
});
if (
props.contentEditable &&
!props.suppressContentEditableWarning &&
props.children != null
) {
console.error(
'A component is `contentEditable` and contains `children` managed by ' +
'React. It is now your responsibility to guarantee that none of ' +
'those nodes are unexpectedly modified or duplicated. This is ' +
'probably not intentional.',
);
}
}
}
function validateFormActionInDevelopment(
tag: string,
key: string,
value: mixed,
props: any,
) {
if (__DEV__) {
if (value == null) {
return;
}
if (tag === 'form') {
if (key === 'formAction') {
console.error(
'You can only pass the formAction prop to <input> or <button>. Use the action prop on <form>.',
);
} else if (typeof value === 'function') {
if (
(props.encType != null || props.method != null) &&
!didWarnFormActionMethod
) {
didWarnFormActionMethod = true;
console.error(
'Cannot specify a encType or method for a form that specifies a ' +
'function as the action. React provides those automatically. ' +
'They will get overridden.',
);
}
if (props.target != null && !didWarnFormActionTarget) {
didWarnFormActionTarget = true;
console.error(
'Cannot specify a target for a form that specifies a function as the action. ' +
'The function will always be executed in the same window.',
);
}
}
} else if (tag === 'input' || tag === 'button') {
if (key === 'action') {
console.error(
'You can only pass the action prop to <form>. Use the formAction prop on <input> or <button>.',
);
} else if (
tag === 'input' &&
props.type !== 'submit' &&
props.type !== 'image' &&
!didWarnFormActionType
) {
didWarnFormActionType = true;
console.error(
'An input can only specify a formAction along with type="submit" or type="image".',
);
} else if (
tag === 'button' &&
props.type != null &&
props.type !== 'submit' &&
!didWarnFormActionType
) {
didWarnFormActionType = true;
console.error(
'A button can only specify a formAction along with type="submit" or no type.',
);
} else if (typeof value === 'function') {
if (props.name != null && !didWarnFormActionName) {
didWarnFormActionName = true;
console.error(
'Cannot specify a "name" prop for a button that specifies a function as a formAction. ' +
'React needs it to encode which action should be invoked. It will get overridden.',
);
}
if (
(props.formEncType != null || props.formMethod != null) &&
!didWarnFormActionMethod
) {
didWarnFormActionMethod = true;
console.error(
'Cannot specify a formEncType or formMethod for a button that specifies a ' +
'function as a formAction. React provides those automatically. They will get overridden.',
);
}
if (props.formTarget != null && !didWarnFormActionTarget) {
didWarnFormActionTarget = true;
console.error(
'Cannot specify a formTarget for a button that specifies a function as a formAction. ' +
'The function will always be executed in the same window.',
);
}
}
} else {
if (key === 'action') {
console.error('You can only pass the action prop to <form>.');
} else {
console.error(
'You can only pass the formAction prop to <input> or <button>.',
);
}
}
}
}
function warnForPropDifference(
propName: string,
serverValue: mixed,
clientValue: mixed,
serverDifferences: {[propName: string]: mixed},
): void {
if (__DEV__) {
if (serverValue === clientValue) {
return;
}
const normalizedClientValue =
normalizeMarkupForTextOrAttribute(clientValue);
const normalizedServerValue =
normalizeMarkupForTextOrAttribute(serverValue);
if (normalizedServerValue === normalizedClientValue) {
return;
}
serverDifferences[propName] = serverValue;
}
}
function warnForExtraAttributes(
domElement: Element,
attributeNames: Set<string>,
serverDifferences: {[propName: string]: mixed},
) {
if (__DEV__) {
attributeNames.forEach(function (attributeName) {
serverDifferences[getPropNameFromAttributeName(attributeName)] =
attributeName === 'style'
? getStylesObjectFromElement(domElement)
: domElement.getAttribute(attributeName);
});
}
}
function warnForInvalidEventListener(registrationName: string, listener: any) {
if (__DEV__) {
if (listener === false) {
console.error(
'Expected `%s` listener to be a function, instead got `false`.\n\n' +
'If you used to conditionally omit it with %s={condition && value}, ' +
'pass %s={condition ? value : undefined} instead.',
registrationName,
registrationName,
registrationName,
);
} else {
console.error(
'Expected `%s` listener to be a function, instead got a value of `%s` type.',
registrationName,
typeof listener,
);
}
}
}
function normalizeHTML(parent: Element, html: string) {
if (__DEV__) {
const testElement =
parent.namespaceURI === MATH_NAMESPACE ||
parent.namespaceURI === SVG_NAMESPACE
? parent.ownerDocument.createElementNS(
(parent.namespaceURI: any),
parent.tagName,
)
: parent.ownerDocument.createElement(parent.tagName);
testElement.innerHTML = html;
return testElement.innerHTML;
}
}
const NORMALIZE_NEWLINES_REGEX = /\r\n?/g;
const NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g;
function normalizeMarkupForTextOrAttribute(markup: mixed): string {
if (__DEV__) {
checkHtmlStringCoercion(markup);
}
const markupString = typeof markup === 'string' ? markup : '' + (markup: any);
return markupString
.replace(NORMALIZE_NEWLINES_REGEX, '\n')
.replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, '');
}
function checkForUnmatchedText(
serverText: string,
clientText: string | number | bigint,
) {
const normalizedClientText = normalizeMarkupForTextOrAttribute(clientText);
const normalizedServerText = normalizeMarkupForTextOrAttribute(serverText);
if (normalizedServerText === normalizedClientText) {
return true;
}
return false;
}
export function trapClickOnNonInteractiveElement(node: HTMLElement) {
node.onclick = noop;
}
const xlinkNamespace = 'http://www.w3.org/1999/xlink';
const xmlNamespace = 'http://www.w3.org/XML/1998/namespace';
function setProp(
domElement: Element,
tag: string,
key: string,
value: mixed,
props: any,
prevValue: mixed,
): void {
switch (key) {
case 'children': {
if (typeof value === 'string') {
if (__DEV__) {
validateTextNesting(value, tag, false);
}
const canSetTextContent =
tag !== 'body' && (tag !== 'textarea' || value !== '');
if (canSetTextContent) {
setTextContent(domElement, value);
}
} else if (typeof value === 'number' || typeof value === 'bigint') {
if (__DEV__) {
validateTextNesting('' + value, tag, false);
}
const canSetTextContent = tag !== 'body';
if (canSetTextContent) {
setTextContent(domElement, '' + value);
}
} else {
return;
}
break;
}
case 'className':
setValueForKnownAttribute(domElement, 'class', value);
break;
case 'tabIndex':
setValueForKnownAttribute(domElement, 'tabindex', value);
break;
case 'dir':
case 'role':
case 'viewBox':
case 'width':
case 'height': {
setValueForKnownAttribute(domElement, key, value);
break;
}
case 'style': {
setValueForStyles(domElement, value, prevValue);
return;
}
case 'data':
if (tag !== 'object') {
setValueForKnownAttribute(domElement, 'data', value);
break;
}
case 'src': {
if (enableSrcObject && typeof value === 'object' && value !== null) {
if (tag === 'img' || tag === 'video' || tag === 'audio') {
try {
setSrcObject(domElement, tag, value);
break;
} catch (x) {
}
} else {
if (__DEV__) {
try {
URL.revokeObjectURL(URL.createObjectURL((value: any)));
if (tag === 'source') {
console.error(
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
'Pass it directly to <img src>, <video src> or <audio src> instead.',
);
} else {
console.error(
'Passing Blob, MediaSource or MediaStream to <%s src> is not supported.',
tag,
);
}
} catch (x) {}
}
}
}
}
case 'href': {
if (
value === '' &&
!(tag === 'a' && key === 'href')
) {
if (__DEV__) {
if (key === 'src') {
console.error(
'An empty string ("") was passed to the %s attribute. ' +
'This may cause the browser to download the whole page again over the network. ' +
'To fix this, either do not render the element at all ' +
'or pass null to %s instead of an empty string.',
key,
key,
);
} else {
console.error(
'An empty string ("") was passed to the %s attribute. ' +
'To fix this, either do not render the element at all ' +
'or pass null to %s instead of an empty string.',
key,
key,
);
}
}
domElement.removeAttribute(key);
break;
}
if (
value == null ||
typeof value === 'function' ||
typeof value === 'symbol' ||
typeof value === 'boolean'
) {
domElement.removeAttribute(key);
break;
}
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
const sanitizedValue = (sanitizeURL(
enableTrustedTypesIntegration ? value : '' + (value: any),
): any);
domElement.setAttribute(key, sanitizedValue);
break;
}
case 'action':
case 'formAction': {
if (__DEV__) {
validateFormActionInDevelopment(tag, key, value, props);
}
if (typeof value === 'function') {
domElement.setAttribute(
key,
"javascript:throw new Error('" +
'A React form was unexpectedly submitted. If you called form.submit() manually, ' +
"consider using form.requestSubmit() instead. If you\\'re trying to use " +
'event.stopPropagation() in a submit event handler, consider also calling ' +
'event.preventDefault().' +
"')",
);
break;
} else if (typeof prevValue === 'function') {
if (key === 'formAction') {
if (tag !== 'input') {
setProp(domElement, tag, 'name', props.name, props, null);
}
setProp(
domElement,
tag,
'formEncType',
props.formEncType,
props,
null,
);
setProp(domElement, tag, 'formMethod', props.formMethod, props, null);
setProp(domElement, tag, 'formTarget', props.formTarget, props, null);
} else {
setProp(domElement, tag, 'encType', props.encType, props, null);
setProp(domElement, tag, 'method', props.method, props, null);
setProp(domElement, tag, 'target', props.target, props, null);
}
}
if (
value == null ||
typeof value === 'symbol' ||
typeof value === 'boolean'
) {
domElement.removeAttribute(key);
break;
}
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
const sanitizedValue = (sanitizeURL(
enableTrustedTypesIntegration ? value : '' + (value: any),
): any);
domElement.setAttribute(key, sanitizedValue);
break;
}
case 'onClick': {
if (value != null) {
if (__DEV__ && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
return;
}
case 'onScroll': {
if (value != null) {
if (__DEV__ && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
listenToNonDelegatedEvent('scroll', domElement);
}
return;
}
case 'onScrollEnd': {
if (value != null) {
if (__DEV__ && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
listenToNonDelegatedEvent('scrollend', domElement);
if (enableScrollEndPolyfill) {
listenToNonDelegatedEvent('scroll', domElement);
}
}
return;
}
case 'dangerouslySetInnerHTML': {
if (value != null) {
if (typeof value !== 'object' || !('__html' in value)) {
throw new Error(
'`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
'Please visit https://react.dev/link/dangerously-set-inner-html ' +
'for more information.',
);
}
const nextHtml: any = value.__html;
if (nextHtml != null) {
if (props.children != null) {
throw new Error(
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
);
}
domElement.innerHTML = nextHtml;
}
}
break;
}
case 'multiple': {
(domElement: any).multiple =
value && typeof value !== 'function' && typeof value !== 'symbol';
break;
}
case 'muted': {
(domElement: any).muted =
value && typeof value !== 'function' && typeof value !== 'symbol';
break;
}
case 'suppressContentEditableWarning':
case 'suppressHydrationWarning':
case 'defaultValue':
case 'defaultChecked':
case 'innerHTML':
case 'ref': {
break;
}
case 'autoFocus': {
break;
}
case 'xlinkHref': {
if (
value == null ||
typeof value === 'function' ||
typeof value === 'boolean' ||
typeof value === 'symbol'
) {
domElement.removeAttribute('xlink:href');
break;
}
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
const sanitizedValue = (sanitizeURL(
enableTrustedTypesIntegration ? value : '' + (value: any),
): any);
domElement.setAttributeNS(xlinkNamespace, 'xlink:href', sanitizedValue);
break;
}
case 'contentEditable':
case 'spellCheck':
case 'draggable':
case 'value':
case 'autoReverse':
case 'externalResourcesRequired':
case 'focusable':
case 'preserveAlpha': {
if (
value != null &&
typeof value !== 'function' &&
typeof value !== 'symbol'
) {
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
domElement.setAttribute(
key,
enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
);
} else {
domElement.removeAttribute(key);
}
break;
}
case 'inert': {
if (__DEV__) {
if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[key]) {
didWarnForNewBooleanPropsWithEmptyValue[key] = true;
console.error(
'Received an empty string for a boolean attribute `%s`. ' +
'This will treat the attribute as if it were false. ' +
'Either pass `false` to silence this warning, or ' +
'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.',
key,
);
}
}
}
case 'allowFullScreen':
case 'async':
case 'autoPlay':
case 'controls':
case 'default':
case 'defer':
case 'disabled':
case 'disablePictureInPicture':
case 'disableRemotePlayback':
case 'formNoValidate':
case 'hidden':
case 'loop':
case 'noModule':
case 'noValidate':
case 'open':
case 'playsInline':
case 'readOnly':
case 'required':
case 'reversed':
case 'scoped':
case 'seamless':
case 'itemScope': {
if (value && typeof value !== 'function' && typeof value !== 'symbol') {
domElement.setAttribute(key, '');
} else {
domElement.removeAttribute(key);
}
break;
}
case 'capture':
case 'download': {
if (value === true) {
domElement.setAttribute(key, '');
} else if (
value !== false &&
value != null &&
typeof value !== 'function' &&
typeof value !== 'symbol'
) {
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
domElement.setAttribute(key, (value: any));
} else {
domElement.removeAttribute(key);
}
break;
}
case 'cols':
case 'rows':
case 'size':
case 'span': {
if (
value != null &&
typeof value !== 'function' &&
typeof value !== 'symbol' &&
!isNaN(value) &&
(value: any) >= 1
) {
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
domElement.setAttribute(key, (value: any));
} else {
domElement.removeAttribute(key);
}
break;
}
case 'rowSpan':
case 'start': {
if (
value != null &&
typeof value !== 'function' &&
typeof value !== 'symbol' &&
!isNaN(value)
) {
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
domElement.setAttribute(key, (value: any));
} else {
domElement.removeAttribute(key);
}
break;
}
case 'popover':
listenToNonDelegatedEvent('beforetoggle', domElement);
listenToNonDelegatedEvent('toggle', domElement);
setValueForAttribute(domElement, 'popover', value);
break;
case 'xlinkActuate':
setValueForNamespacedAttribute(
domElement,
xlinkNamespace,
'xlink:actuate',
value,
);
break;
case 'xlinkArcrole':
setValueForNamespacedAttribute(
domElement,
xlinkNamespace,
'xlink:arcrole',
value,
);
break;
case 'xlinkRole':
setValueForNamespacedAttribute(
domElement,
xlinkNamespace,
'xlink:role',
value,
);
break;
case 'xlinkShow':
setValueForNamespacedAttribute(
domElement,
xlinkNamespace,
'xlink:show',
value,
);
break;
case 'xlinkTitle':
setValueForNamespacedAttribute(
domElement,
xlinkNamespace,
'xlink:title',
value,
);
break;
case 'xlinkType':
setValueForNamespacedAttribute(
domElement,
xlinkNamespace,
'xlink:type',
value,
);
break;
case 'xmlBase':
setValueForNamespacedAttribute(
domElement,
xmlNamespace,
'xml:base',
value,
);
break;
case 'xmlLang':
setValueForNamespacedAttribute(
domElement,
xmlNamespace,
'xml:lang',
value,
);
break;
case 'xmlSpace':
setValueForNamespacedAttribute(
domElement,
xmlNamespace,
'xml:space',
value,
);
break;
case 'is': {
if (__DEV__) {
if (prevValue != null) {
console.error(
'Cannot update the "is" prop after it has been initialized.',
);
}
}
setValueForAttribute(domElement, 'is', value);
break;
}
case 'innerText':
case 'textContent':
return;
case 'popoverTarget':
if (__DEV__) {
if (
!didWarnPopoverTargetObject &&
value != null &&
typeof value === 'object'
) {
didWarnPopoverTargetObject = true;
console.error(
'The `popoverTarget` prop expects the ID of an Element as a string. Received %s instead.',
value,
);
}
}
default: {
if (
key.length > 2 &&
(key[0] === 'o' || key[0] === 'O') &&
(key[1] === 'n' || key[1] === 'N')
) {
if (
__DEV__ &&
registrationNameDependencies.hasOwnProperty(key) &&
value != null &&
typeof value !== 'function'
) {
warnForInvalidEventListener(key, value);
}
return;
} else {
const attributeName = getAttributeAlias(key);
setValueForAttribute(domElement, attributeName, value);
}
}
}
trackHostMutation();
}
function setPropOnCustomElement(
domElement: Element,
tag: string,
key: string,
value: mixed,
props: any,
prevValue: mixed,
): void {
switch (key) {
case 'style': {
setValueForStyles(domElement, value, prevValue);
return;
}
case 'dangerouslySetInnerHTML': {
if (value != null) {
if (typeof value !== 'object' || !('__html' in value)) {
throw new Error(
'`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
'Please visit https://react.dev/link/dangerously-set-inner-html ' +
'for more information.',
);
}
const nextHtml: any = value.__html;
if (nextHtml != null) {
if (props.children != null) {
throw new Error(
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
);
}
domElement.innerHTML = nextHtml;
}
}
break;
}
case 'children': {
if (typeof value === 'string') {
setTextContent(domElement, value);
} else if (typeof value === 'number' || typeof value === 'bigint') {
setTextContent(domElement, '' + value);
} else {
return;
}
break;
}
case 'onScroll': {
if (value != null) {
if (__DEV__ && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
listenToNonDelegatedEvent('scroll', domElement);
}
return;
}
case 'onScrollEnd': {
if (value != null) {
if (__DEV__ && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
listenToNonDelegatedEvent('scrollend', domElement);
if (enableScrollEndPolyfill) {
listenToNonDelegatedEvent('scroll', domElement);
}
}
return;
}
case 'onClick': {
if (value != null) {
if (__DEV__ && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
return;
}
case 'suppressContentEditableWarning':
case 'suppressHydrationWarning':
case 'innerHTML':
case 'ref': {
return;
}
case 'innerText':
case 'textContent':
return;
default: {
if (registrationNameDependencies.hasOwnProperty(key)) {
if (__DEV__ && value != null && typeof value !== 'function') {
warnForInvalidEventListener(key, value);
}
return;
} else {
setValueForPropertyOnCustomComponent(domElement, key, value);
return;
}
}
}
trackHostMutation();
}
export function setInitialProperties(
domElement: Element,
tag: string,
props: Object,
): void {
if (__DEV__) {
validatePropertiesInDevelopment(tag, props);
}
switch (tag) {
case 'div':
case 'span':
case 'svg':
case 'path':
case 'a':
case 'g':
case 'p':
case 'li': {
break;
}
case 'img': {
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
let hasSrc = false;
let hasSrcSet = false;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'src':
hasSrc = true;
break;
case 'srcSet':
hasSrcSet = true;
break;
case 'children':
case 'dangerouslySetInnerHTML': {
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
if (hasSrcSet) {
setProp(domElement, tag, 'srcSet', props.srcSet, props, null);
}
if (hasSrc) {
setProp(domElement, tag, 'src', props.src, props, null);
}
return;
}
case 'input': {
if (__DEV__) {
checkControlledValueProps('input', props);
}
listenToNonDelegatedEvent('invalid', domElement);
let name = null;
let type = null;
let value = null;
let defaultValue = null;
let checked = null;
let defaultChecked = null;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'name': {
name = propValue;
break;
}
case 'type': {
type = propValue;
break;
}
case 'checked': {
checked = propValue;
break;
}
case 'defaultChecked': {
defaultChecked = propValue;
break;
}
case 'value': {
value = propValue;
break;
}
case 'defaultValue': {
defaultValue = propValue;
break;
}
case 'children':
case 'dangerouslySetInnerHTML': {
if (propValue != null) {
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
break;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
validateInputProps(domElement, props);
initInput(
domElement,
value,
defaultValue,
checked,
defaultChecked,
type,
name,
false,
);
return;
}
case 'select': {
if (__DEV__) {
checkControlledValueProps('select', props);
}
listenToNonDelegatedEvent('invalid', domElement);
let value = null;
let defaultValue = null;
let multiple = null;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'value': {
value = propValue;
break;
}
case 'defaultValue': {
defaultValue = propValue;
break;
}
case 'multiple': {
multiple = propValue;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
validateSelectProps(domElement, props);
initSelect(domElement, value, defaultValue, multiple);
return;
}
case 'textarea': {
if (__DEV__) {
checkControlledValueProps('textarea', props);
}
listenToNonDelegatedEvent('invalid', domElement);
let value = null;
let defaultValue = null;
let children = null;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'value': {
value = propValue;
break;
}
case 'defaultValue': {
defaultValue = propValue;
break;
}
case 'children': {
children = propValue;
break;
}
case 'dangerouslySetInnerHTML': {
if (propValue != null) {
throw new Error(
'`dangerouslySetInnerHTML` does not make sense on <textarea>.',
);
}
break;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
validateTextareaProps(domElement, props);
initTextarea(domElement, value, defaultValue, children);
return;
}
case 'option': {
validateOptionProps(domElement, props);
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'selected': {
(domElement: any).selected =
propValue &&
typeof propValue !== 'function' &&
typeof propValue !== 'symbol';
break;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
return;
}
case 'dialog': {
listenToNonDelegatedEvent('beforetoggle', domElement);
listenToNonDelegatedEvent('toggle', domElement);
listenToNonDelegatedEvent('cancel', domElement);
listenToNonDelegatedEvent('close', domElement);
break;
}
case 'iframe':
case 'object': {
listenToNonDelegatedEvent('load', domElement);
break;
}
case 'video':
case 'audio': {
for (let i = 0; i < mediaEventTypes.length; i++) {
listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
}
break;
}
case 'image': {
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
break;
}
case 'details': {
listenToNonDelegatedEvent('toggle', domElement);
break;
}
case 'embed':
case 'source':
case 'link': {
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
}
case 'area':
case 'base':
case 'br':
case 'col':
case 'hr':
case 'keygen':
case 'meta':
case 'param':
case 'track':
case 'wbr':
case 'menuitem': {
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'children':
case 'dangerouslySetInnerHTML': {
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
return;
}
default: {
if (isCustomElement(tag, props)) {
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue === undefined) {
continue;
}
setPropOnCustomElement(
domElement,
tag,
propKey,
propValue,
props,
undefined,
);
}
return;
}
}
}
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
setProp(domElement, tag, propKey, propValue, props, null);
}
}
export function updateProperties(
domElement: Element,
tag: string,
lastProps: Object,
nextProps: Object,
): void {
if (__DEV__) {
validatePropertiesInDevelopment(tag, nextProps);
}
switch (tag) {
case 'div':
case 'span':
case 'svg':
case 'path':
case 'a':
case 'g':
case 'p':
case 'li': {
break;
}
case 'input': {
let name = null;
let type = null;
let value = null;
let defaultValue = null;
let lastDefaultValue = null;
let checked = null;
let defaultChecked = null;
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (lastProps.hasOwnProperty(propKey) && lastProp != null) {
switch (propKey) {
case 'checked': {
break;
}
case 'value': {
break;
}
case 'defaultValue': {
lastDefaultValue = lastProp;
}
default: {
if (!nextProps.hasOwnProperty(propKey))
setProp(domElement, tag, propKey, null, nextProps, lastProp);
}
}
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
(nextProp != null || lastProp != null)
) {
switch (propKey) {
case 'type': {
if (nextProp !== lastProp) {
trackHostMutation();
}
type = nextProp;
break;
}
case 'name': {
if (nextProp !== lastProp) {
trackHostMutation();
}
name = nextProp;
break;
}
case 'checked': {
if (nextProp !== lastProp) {
trackHostMutation();
}
checked = nextProp;
break;
}
case 'defaultChecked': {
if (nextProp !== lastProp) {
trackHostMutation();
}
defaultChecked = nextProp;
break;
}
case 'value': {
if (nextProp !== lastProp) {
trackHostMutation();
}
value = nextProp;
break;
}
case 'defaultValue': {
if (nextProp !== lastProp) {
trackHostMutation();
}
defaultValue = nextProp;
break;
}
case 'children':
case 'dangerouslySetInnerHTML': {
if (nextProp != null) {
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
break;
}
default: {
if (nextProp !== lastProp)
setProp(
domElement,
tag,
propKey,
nextProp,
nextProps,
lastProp,
);
}
}
}
}
if (__DEV__) {
const wasControlled =
lastProps.type === 'checkbox' || lastProps.type === 'radio'
? lastProps.checked != null
: lastProps.value != null;
const isControlled =
nextProps.type === 'checkbox' || nextProps.type === 'radio'
? nextProps.checked != null
: nextProps.value != null;
if (
!wasControlled &&
isControlled &&
!didWarnUncontrolledToControlled
) {
console.error(
'A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, which should not happen. ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components',
);
didWarnUncontrolledToControlled = true;
}
if (
wasControlled &&
!isControlled &&
!didWarnControlledToUncontrolled
) {
console.error(
'A component is changing a controlled input to be uncontrolled. ' +
'This is likely caused by the value changing from a defined to ' +
'undefined, which should not happen. ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://react.dev/link/controlled-components',
);
didWarnControlledToUncontrolled = true;
}
}
updateInput(
domElement,
value,
defaultValue,
lastDefaultValue,
checked,
defaultChecked,
type,
name,
);
return;
}
case 'select': {
let value = null;
let defaultValue = null;
let multiple = null;
let wasMultiple = null;
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (lastProps.hasOwnProperty(propKey) && lastProp != null) {
switch (propKey) {
case 'value': {
break;
}
case 'multiple': {
wasMultiple = lastProp;
}
default: {
if (!nextProps.hasOwnProperty(propKey)) {
setProp(domElement, tag, propKey, null, nextProps, lastProp);
}
}
}
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
(nextProp != null || lastProp != null)
) {
switch (propKey) {
case 'value': {
if (nextProp !== lastProp) {
trackHostMutation();
}
value = nextProp;
break;
}
case 'defaultValue': {
if (nextProp !== lastProp) {
trackHostMutation();
}
defaultValue = nextProp;
break;
}
case 'multiple': {
if (nextProp !== lastProp) {
trackHostMutation();
}
multiple = nextProp;
}
default: {
if (nextProp !== lastProp)
setProp(
domElement,
tag,
propKey,
nextProp,
nextProps,
lastProp,
);
}
}
}
}
updateSelect(domElement, value, defaultValue, multiple, wasMultiple);
return;
}
case 'textarea': {
let value = null;
let defaultValue = null;
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (
lastProps.hasOwnProperty(propKey) &&
lastProp != null &&
!nextProps.hasOwnProperty(propKey)
) {
switch (propKey) {
case 'value': {
break;
}
case 'children': {
break;
}
default: {
setProp(domElement, tag, propKey, null, nextProps, lastProp);
}
}
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
(nextProp != null || lastProp != null)
) {
switch (propKey) {
case 'value': {
if (nextProp !== lastProp) {
trackHostMutation();
}
value = nextProp;
break;
}
case 'defaultValue': {
if (nextProp !== lastProp) {
trackHostMutation();
}
defaultValue = nextProp;
break;
}
case 'children': {
break;
}
case 'dangerouslySetInnerHTML': {
if (nextProp != null) {
throw new Error(
'`dangerouslySetInnerHTML` does not make sense on <textarea>.',
);
}
break;
}
default: {
if (nextProp !== lastProp)
setProp(
domElement,
tag,
propKey,
nextProp,
nextProps,
lastProp,
);
}
}
}
}
updateTextarea(domElement, value, defaultValue);
return;
}
case 'option': {
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (
lastProps.hasOwnProperty(propKey) &&
lastProp != null &&
!nextProps.hasOwnProperty(propKey)
) {
switch (propKey) {
case 'selected': {
(domElement: any).selected = false;
break;
}
default: {
setProp(domElement, tag, propKey, null, nextProps, lastProp);
}
}
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
nextProp !== lastProp &&
(nextProp != null || lastProp != null)
) {
switch (propKey) {
case 'selected': {
if (nextProp !== lastProp) {
trackHostMutation();
}
(domElement: any).selected =
nextProp &&
typeof nextProp !== 'function' &&
typeof nextProp !== 'symbol';
break;
}
default: {
setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
}
}
}
}
return;
}
case 'img':
case 'link':
case 'area':
case 'base':
case 'br':
case 'col':
case 'embed':
case 'hr':
case 'keygen':
case 'meta':
case 'param':
case 'source':
case 'track':
case 'wbr':
case 'menuitem': {
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (
lastProps.hasOwnProperty(propKey) &&
lastProp != null &&
!nextProps.hasOwnProperty(propKey)
) {
setProp(domElement, tag, propKey, null, nextProps, lastProp);
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
nextProp !== lastProp &&
(nextProp != null || lastProp != null)
) {
switch (propKey) {
case 'children':
case 'dangerouslySetInnerHTML': {
if (nextProp != null) {
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
break;
}
default: {
setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
}
}
}
}
return;
}
default: {
if (isCustomElement(tag, nextProps)) {
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (
lastProps.hasOwnProperty(propKey) &&
lastProp !== undefined &&
!nextProps.hasOwnProperty(propKey)
) {
setPropOnCustomElement(
domElement,
tag,
propKey,
undefined,
nextProps,
lastProp,
);
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
nextProp !== lastProp &&
(nextProp !== undefined || lastProp !== undefined)
) {
setPropOnCustomElement(
domElement,
tag,
propKey,
nextProp,
nextProps,
lastProp,
);
}
}
return;
}
}
}
for (const propKey in lastProps) {
const lastProp = lastProps[propKey];
if (
lastProps.hasOwnProperty(propKey) &&
lastProp != null &&
!nextProps.hasOwnProperty(propKey)
) {
setProp(domElement, tag, propKey, null, nextProps, lastProp);
}
}
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps[propKey];
if (
nextProps.hasOwnProperty(propKey) &&
nextProp !== lastProp &&
(nextProp != null || lastProp != null)
) {
setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
}
}
}
function getPossibleStandardName(propName: string): string | null {
if (__DEV__) {
const lowerCasedName = propName.toLowerCase();
if (!possibleStandardNames.hasOwnProperty(lowerCasedName)) {
return null;
}
return possibleStandardNames[lowerCasedName] || null;
}
return null;
}
function getPropNameFromAttributeName(attrName: string): string {
switch (attrName) {
case 'class':
return 'className';
case 'for':
return 'htmlFor';
default:
return attrName;
}
}
export function getPropsFromElement(domElement: Element): Object {
const serverDifferences: {[propName: string]: mixed} = {};
const attributes = domElement.attributes;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
serverDifferences[getPropNameFromAttributeName(attr.name)] =
attr.name.toLowerCase() === 'style'
? getStylesObjectFromElement(domElement)
: attr.value;
}
return serverDifferences;
}
function getStylesObjectFromElement(domElement: Element): {
[styleName: string]: string,
} {
const serverValueInObjectForm: {[prop: string]: string} = {};
const style = ((domElement: any): HTMLElement).style;
for (let i = 0; i < style.length; i++) {
const styleName: string = style[i];
serverValueInObjectForm[styleName] = style.getPropertyValue(styleName);
}
return serverValueInObjectForm;
}
function diffHydratedStyles(
domElement: Element,
value: mixed,
serverDifferences: {[propName: string]: mixed},
): void {
if (value != null && typeof value !== 'object') {
if (__DEV__) {
console.error(
'The `style` prop expects a mapping from style properties to values, ' +
"not a string. For example, style={{marginRight: spacing + 'em'}} when " +
'using JSX.',
);
}
return;
}
const clientValue = createDangerousStringForStyles(value);
const serverValue = domElement.getAttribute('style');
if (serverValue === clientValue) {
return;
}
const normalizedClientValue = normalizeMarkupForTextOrAttribute(clientValue);
const normalizedServerValue = normalizeMarkupForTextOrAttribute(serverValue);
if (normalizedServerValue === normalizedClientValue) {
return;
}
serverDifferences.style = getStylesObjectFromElement(domElement);
}
function hydrateAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol':
case 'boolean':
return;
}
} else {
if (value == null) {
} else {
switch (typeof value) {
case 'function':
case 'symbol':
case 'boolean':
break;
default: {
if (__DEV__) {
checkAttributeStringCoercion(value, propKey);
}
if (serverValue === '' + value) {
return;
}
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydrateBooleanAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'function':
case 'symbol':
return;
}
if (!value) {
return;
}
} else {
switch (typeof value) {
case 'function':
case 'symbol':
break;
default: {
if (value) {
return;
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydrateOverloadedBooleanAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol':
return;
default:
if (value === false) {
return;
}
}
} else {
if (value == null) {
} else {
switch (typeof value) {
case 'function':
case 'symbol':
break;
case 'boolean':
if (value === true && serverValue === '') {
return;
}
break;
default: {
if (__DEV__) {
checkAttributeStringCoercion(value, propKey);
}
if (serverValue === '' + value) {
return;
}
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydrateBooleanishAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol':
return;
}
} else {
if (value == null) {
} else {
switch (typeof value) {
case 'function':
case 'symbol':
break;
default: {
if (__DEV__) {
checkAttributeStringCoercion(value, attributeName);
}
if (serverValue === '' + (value: any)) {
return;
}
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydrateNumericAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol':
case 'boolean':
return;
default:
if (isNaN(value)) {
return;
}
}
} else {
if (value == null) {
} else {
switch (typeof value) {
case 'function':
case 'symbol':
case 'boolean':
break;
default: {
if (isNaN(value)) {
break;
}
if (__DEV__) {
checkAttributeStringCoercion(value, propKey);
}
if (serverValue === '' + value) {
return;
}
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydratePositiveNumericAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol':
case 'boolean':
return;
default:
if (isNaN(value) || value < 1) {
return;
}
}
} else {
if (value == null) {
} else {
switch (typeof value) {
case 'function':
case 'symbol':
case 'boolean':
break;
default: {
if (isNaN(value) || value < 1) {
break;
}
if (__DEV__) {
checkAttributeStringCoercion(value, propKey);
}
if (serverValue === '' + value) {
return;
}
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydrateSanitizedAttribute(
domElement: Element,
propKey: string,
attributeName: string,
value: any,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue === null) {
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol':
case 'boolean':
return;
}
} else {
if (value == null) {
} else {
switch (typeof value) {
case 'function':
case 'symbol':
case 'boolean':
break;
default: {
if (__DEV__) {
checkAttributeStringCoercion(value, propKey);
}
const sanitizedValue = sanitizeURL('' + value);
if (serverValue === sanitizedValue) {
return;
}
}
}
}
}
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
function hydrateSrcObjectAttribute(
domElement: Element,
value: Blob,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
): void {
const attributeName = 'src';
extraAttributes.delete(attributeName);
const serverValue = domElement.getAttribute(attributeName);
if (serverValue != null && value != null) {
const size = value.size;
const type = value.type;
if (typeof size === 'number' && typeof type === 'string') {
if (serverValue.indexOf('data:' + type + ';base64,') === 0) {
const prefixLength = 5 + type.length + 8;
let byteLength = ((serverValue.length - prefixLength) / 4) * 3;
if (serverValue[serverValue.length - 1] === '=') {
byteLength--;
}
if (serverValue[serverValue.length - 2] === '=') {
byteLength--;
}
if (byteLength === size) {
return;
}
}
}
}
warnForPropDifference('src', serverValue, value, serverDifferences);
}
function diffHydratedCustomComponent(
domElement: Element,
tag: string,
props: Object,
hostContext: HostContext,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
) {
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const value = props[propKey];
if (value == null) {
continue;
}
if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (typeof value !== 'function') {
warnForInvalidEventListener(propKey, value);
}
continue;
}
if (props.suppressHydrationWarning === true) {
continue;
}
switch (propKey) {
case 'children': {
if (typeof value === 'string' || typeof value === 'number') {
warnForPropDifference(
'children',
domElement.textContent,
value,
serverDifferences,
);
}
continue;
}
case 'suppressContentEditableWarning':
case 'suppressHydrationWarning':
case 'defaultValue':
case 'defaultChecked':
case 'innerHTML':
case 'ref':
continue;
case 'dangerouslySetInnerHTML':
const serverHTML = domElement.innerHTML;
const nextHtml = value ? value.__html : undefined;
if (nextHtml != null) {
const expectedHTML = normalizeHTML(domElement, nextHtml);
warnForPropDifference(
propKey,
serverHTML,
expectedHTML,
serverDifferences,
);
}
continue;
case 'style':
extraAttributes.delete(propKey);
diffHydratedStyles(domElement, value, serverDifferences);
continue;
case 'offsetParent':
case 'offsetTop':
case 'offsetLeft':
case 'offsetWidth':
case 'offsetHeight':
case 'isContentEditable':
case 'outerText':
case 'outerHTML':
extraAttributes.delete(propKey.toLowerCase());
if (__DEV__) {
console.error(
'Assignment to read-only property will result in a no-op: `%s`',
propKey,
);
}
continue;
case 'className':
extraAttributes.delete('class');
const serverValue = getValueForAttributeOnCustomComponent(
domElement,
'class',
value,
);
warnForPropDifference(
'className',
serverValue,
value,
serverDifferences,
);
continue;
default: {
const hostContextDev: HostContextDev = (hostContext: any);
const hostContextProd = hostContextDev.context;
if (
hostContextProd === HostContextNamespaceNone &&
tag !== 'svg' &&
tag !== 'math'
) {
extraAttributes.delete(propKey.toLowerCase());
} else {
extraAttributes.delete(propKey);
}
const valueOnCustomComponent = getValueForAttributeOnCustomComponent(
domElement,
propKey,
value,
);
warnForPropDifference(
propKey,
valueOnCustomComponent,
value,
serverDifferences,
);
}
}
}
}
const EXPECTED_FORM_ACTION_URL =
"javascript:throw new Error('React form unexpectedly submitted.')";
function diffHydratedGenericElement(
domElement: Element,
tag: string,
props: Object,
hostContext: HostContext,
extraAttributes: Set<string>,
serverDifferences: {[propName: string]: mixed},
) {
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const value = props[propKey];
if (value == null) {
continue;
}
if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (typeof value !== 'function') {
warnForInvalidEventListener(propKey, value);
}
continue;
}
if (props.suppressHydrationWarning === true) {
continue;
}
switch (propKey) {
case 'children': {
if (typeof value === 'string' || typeof value === 'number') {
warnForPropDifference(
'children',
domElement.textContent,
value,
serverDifferences,
);
}
continue;
}
case 'suppressContentEditableWarning':
case 'suppressHydrationWarning':
case 'value':
case 'checked':
case 'selected':
case 'defaultValue':
case 'defaultChecked':
case 'innerHTML':
case 'ref':
continue;
case 'dangerouslySetInnerHTML':
const serverHTML = domElement.innerHTML;
const nextHtml = value ? value.__html : undefined;
if (nextHtml != null) {
const expectedHTML = normalizeHTML(domElement, nextHtml);
if (serverHTML !== expectedHTML) {
serverDifferences[propKey] = {
__html: serverHTML,
};
}
}
continue;
case 'className':
hydrateAttribute(
domElement,
propKey,
'class',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'tabIndex':
hydrateAttribute(
domElement,
propKey,
'tabindex',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'style':
extraAttributes.delete(propKey);
diffHydratedStyles(domElement, value, serverDifferences);
continue;
case 'multiple': {
extraAttributes.delete(propKey);
const serverValue = (domElement: any).multiple;
warnForPropDifference(propKey, serverValue, value, serverDifferences);
continue;
}
case 'muted': {
extraAttributes.delete(propKey);
const serverValue = (domElement: any).muted;
warnForPropDifference(propKey, serverValue, value, serverDifferences);
continue;
}
case 'autoFocus': {
extraAttributes.delete('autofocus');
const serverValue = (domElement: any).autofocus;
warnForPropDifference(propKey, serverValue, value, serverDifferences);
continue;
}
case 'data':
if (tag !== 'object') {
extraAttributes.delete(propKey);
const serverValue = (domElement: any).getAttribute('data');
warnForPropDifference(propKey, serverValue, value, serverDifferences);
continue;
}
case 'src': {
if (enableSrcObject && typeof value === 'object' && value !== null) {
if (tag === 'img' || tag === 'video' || tag === 'audio') {
try {
URL.revokeObjectURL(URL.createObjectURL((value: any)));
hydrateSrcObjectAttribute(
domElement,
value,
extraAttributes,
serverDifferences,
);
continue;
} catch (x) {
}
} else {
if (__DEV__) {
try {
URL.revokeObjectURL(URL.createObjectURL((value: any)));
if (tag === 'source') {
console.error(
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
'Pass it directly to <img src>, <video src> or <audio src> instead.',
);
} else {
console.error(
'Passing Blob, MediaSource or MediaStream to <%s src> is not supported.',
tag,
);
}
} catch (x) {}
}
}
}
}
case 'href':
if (
value === '' &&
!(tag === 'a' && propKey === 'href') &&
!(tag === 'object' && propKey === 'data')
) {
if (__DEV__) {
if (propKey === 'src') {
console.error(
'An empty string ("") was passed to the %s attribute. ' +
'This may cause the browser to download the whole page again over the network. ' +
'To fix this, either do not render the element at all ' +
'or pass null to %s instead of an empty string.',
propKey,
propKey,
);
} else {
console.error(
'An empty string ("") was passed to the %s attribute. ' +
'To fix this, either do not render the element at all ' +
'or pass null to %s instead of an empty string.',
propKey,
propKey,
);
}
}
continue;
}
hydrateSanitizedAttribute(
domElement,
propKey,
propKey,
value,
extraAttributes,
serverDifferences,
);
continue;
case 'action':
case 'formAction': {
const serverValue = domElement.getAttribute(propKey);
if (typeof value === 'function') {
extraAttributes.delete(propKey.toLowerCase());
if (propKey === 'formAction') {
extraAttributes.delete('name');
extraAttributes.delete('formenctype');
extraAttributes.delete('formmethod');
extraAttributes.delete('formtarget');
} else {
extraAttributes.delete('enctype');
extraAttributes.delete('method');
extraAttributes.delete('target');
}
continue;
} else if (serverValue === EXPECTED_FORM_ACTION_URL) {
extraAttributes.delete(propKey.toLowerCase());
warnForPropDifference(propKey, 'function', value, serverDifferences);
continue;
}
hydrateSanitizedAttribute(
domElement,
propKey,
propKey.toLowerCase(),
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'xlinkHref':
hydrateSanitizedAttribute(
domElement,
propKey,
'xlink:href',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'contentEditable': {
hydrateBooleanishAttribute(
domElement,
propKey,
'contenteditable',
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'spellCheck': {
hydrateBooleanishAttribute(
domElement,
propKey,
'spellcheck',
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'draggable':
case 'autoReverse':
case 'externalResourcesRequired':
case 'focusable':
case 'preserveAlpha': {
hydrateBooleanishAttribute(
domElement,
propKey,
propKey,
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'allowFullScreen':
case 'async':
case 'autoPlay':
case 'controls':
case 'default':
case 'defer':
case 'disabled':
case 'disablePictureInPicture':
case 'disableRemotePlayback':
case 'formNoValidate':
case 'hidden':
case 'loop':
case 'noModule':
case 'noValidate':
case 'open':
case 'playsInline':
case 'readOnly':
case 'required':
case 'reversed':
case 'scoped':
case 'seamless':
case 'itemScope': {
hydrateBooleanAttribute(
domElement,
propKey,
propKey.toLowerCase(),
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'capture':
case 'download': {
hydrateOverloadedBooleanAttribute(
domElement,
propKey,
propKey,
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'cols':
case 'rows':
case 'size':
case 'span': {
hydratePositiveNumericAttribute(
domElement,
propKey,
propKey,
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'rowSpan': {
hydrateNumericAttribute(
domElement,
propKey,
'rowspan',
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'start': {
hydrateNumericAttribute(
domElement,
propKey,
propKey,
value,
extraAttributes,
serverDifferences,
);
continue;
}
case 'xHeight':
hydrateAttribute(
domElement,
propKey,
'x-height',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xlinkActuate':
hydrateAttribute(
domElement,
propKey,
'xlink:actuate',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xlinkArcrole':
hydrateAttribute(
domElement,
propKey,
'xlink:arcrole',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xlinkRole':
hydrateAttribute(
domElement,
propKey,
'xlink:role',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xlinkShow':
hydrateAttribute(
domElement,
propKey,
'xlink:show',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xlinkTitle':
hydrateAttribute(
domElement,
propKey,
'xlink:title',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xlinkType':
hydrateAttribute(
domElement,
propKey,
'xlink:type',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xmlBase':
hydrateAttribute(
domElement,
propKey,
'xml:base',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xmlLang':
hydrateAttribute(
domElement,
propKey,
'xml:lang',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'xmlSpace':
hydrateAttribute(
domElement,
propKey,
'xml:space',
value,
extraAttributes,
serverDifferences,
);
continue;
case 'inert':
if (__DEV__) {
if (
value === '' &&
!didWarnForNewBooleanPropsWithEmptyValue[propKey]
) {
didWarnForNewBooleanPropsWithEmptyValue[propKey] = true;
console.error(
'Received an empty string for a boolean attribute `%s`. ' +
'This will treat the attribute as if it were false. ' +
'Either pass `false` to silence this warning, or ' +
'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.',
propKey,
);
}
}
hydrateBooleanAttribute(
domElement,
propKey,
propKey,
value,
extraAttributes,
serverDifferences,
);
continue;
default: {
if (
propKey.length > 2 &&
(propKey[0] === 'o' || propKey[0] === 'O') &&
(propKey[1] === 'n' || propKey[1] === 'N')
) {
continue;
}
const attributeName = getAttributeAlias(propKey);
let isMismatchDueToBadCasing = false;
const hostContextDev: HostContextDev = (hostContext: any);
const hostContextProd = hostContextDev.context;
if (
hostContextProd === HostContextNamespaceNone &&
tag !== 'svg' &&
tag !== 'math'
) {
extraAttributes.delete(attributeName.toLowerCase());
} else {
const standardName = getPossibleStandardName(propKey);
if (standardName !== null && standardName !== propKey) {
isMismatchDueToBadCasing = true;
extraAttributes.delete(standardName);
}
extraAttributes.delete(attributeName);
}
const serverValue = getValueForAttribute(
domElement,
attributeName,
value,
);
if (!isMismatchDueToBadCasing) {
warnForPropDifference(propKey, serverValue, value, serverDifferences);
}
}
}
}
}
export function hydrateProperties(
domElement: Element,
tag: string,
props: Object,
hostContext: HostContext,
): boolean {
if (__DEV__) {
validatePropertiesInDevelopment(tag, props);
}
switch (tag) {
case 'dialog':
listenToNonDelegatedEvent('cancel', domElement);
listenToNonDelegatedEvent('close', domElement);
break;
case 'iframe':
case 'object':
case 'embed':
listenToNonDelegatedEvent('load', domElement);
break;
case 'video':
case 'audio':
for (let i = 0; i < mediaEventTypes.length; i++) {
listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
}
break;
case 'source':
listenToNonDelegatedEvent('error', domElement);
break;
case 'img':
case 'image':
case 'link':
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
break;
case 'details':
listenToNonDelegatedEvent('toggle', domElement);
break;
case 'input':
if (__DEV__) {
checkControlledValueProps('input', props);
}
listenToNonDelegatedEvent('invalid', domElement);
validateInputProps(domElement, props);
if (!enableHydrationChangeEvent) {
initInput(
domElement,
props.value,
props.defaultValue,
props.checked,
props.defaultChecked,
props.type,
props.name,
true,
);
}
break;
case 'option':
validateOptionProps(domElement, props);
break;
case 'select':
if (__DEV__) {
checkControlledValueProps('select', props);
}
listenToNonDelegatedEvent('invalid', domElement);
validateSelectProps(domElement, props);
break;
case 'textarea':
if (__DEV__) {
checkControlledValueProps('textarea', props);
}
listenToNonDelegatedEvent('invalid', domElement);
validateTextareaProps(domElement, props);
if (!enableHydrationChangeEvent) {
initTextarea(
domElement,
props.value,
props.defaultValue,
props.children,
);
}
break;
}
const children = props.children;
if (
typeof children === 'string' ||
typeof children === 'number' ||
typeof children === 'bigint'
) {
if (
domElement.textContent !== '' + children &&
props.suppressHydrationWarning !== true &&
!checkForUnmatchedText(domElement.textContent, children)
) {
return false;
}
}
if (props.popover != null) {
listenToNonDelegatedEvent('beforetoggle', domElement);
listenToNonDelegatedEvent('toggle', domElement);
}
if (props.onScroll != null) {
listenToNonDelegatedEvent('scroll', domElement);
}
if (props.onScrollEnd != null) {
listenToNonDelegatedEvent('scrollend', domElement);
if (enableScrollEndPolyfill) {
listenToNonDelegatedEvent('scroll', domElement);
}
}
if (props.onClick != null) {
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
return true;
}
export function diffHydratedProperties(
domElement: Element,
tag: string,
props: Object,
hostContext: HostContext,
): null | Object {
const serverDifferences: {[propName: string]: mixed} = {};
if (__DEV__) {
const extraAttributes: Set<string> = new Set();
const attributes = domElement.attributes;
for (let i = 0; i < attributes.length; i++) {
const name = attributes[i].name.toLowerCase();
switch (name) {
case 'value':
break;
case 'checked':
break;
case 'selected':
break;
case 'vt-name':
case 'vt-update':
case 'vt-enter':
case 'vt-exit':
case 'vt-share':
if (enableViewTransition) {
break;
}
default:
extraAttributes.add(attributes[i].name);
}
}
if (isCustomElement(tag, props)) {
diffHydratedCustomComponent(
domElement,
tag,
props,
hostContext,
extraAttributes,
serverDifferences,
);
} else {
diffHydratedGenericElement(
domElement,
tag,
props,
hostContext,
extraAttributes,
serverDifferences,
);
}
if (extraAttributes.size > 0 && props.suppressHydrationWarning !== true) {
warnForExtraAttributes(domElement, extraAttributes, serverDifferences);
}
}
if (Object.keys(serverDifferences).length === 0) {
return null;
}
return serverDifferences;
}
export function hydrateText(
textNode: Text,
text: string,
parentProps: null | Object,
): boolean {
const isDifferent = textNode.nodeValue !== text;
if (
isDifferent &&
(parentProps === null || parentProps.suppressHydrationWarning !== true) &&
!checkForUnmatchedText(textNode.nodeValue, text)
) {
return false;
}
return true;
}
export function diffHydratedText(textNode: Text, text: string): null | string {
if (textNode.nodeValue === text) {
return null;
}
const normalizedClientText = normalizeMarkupForTextOrAttribute(text);
const normalizedServerText = normalizeMarkupForTextOrAttribute(
textNode.nodeValue,
);
if (normalizedServerText === normalizedClientText) {
return null;
}
return textNode.nodeValue;
}
export function restoreControlledState(
domElement: Element,
tag: string,
props: Object,
): void {
switch (tag) {
case 'input':
restoreControlledInputState(domElement, props);
return;
case 'textarea':
restoreControlledTextareaState(domElement, props);
return;
case 'select':
restoreControlledSelectState(domElement, props);
return;
}
}