diff --git a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js index ac311c72e01b6..4066055e0197f 100644 --- a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js +++ b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js @@ -140,33 +140,6 @@ export function setValueForAttribute( } } -export function setValueForKnownAttribute( - node: Element, - name: string, - value: mixed, -) { - if (value === null) { - node.removeAttribute(name); - return; - } - switch (typeof value) { - case 'undefined': - case 'function': - case 'symbol': - case 'boolean': { - node.removeAttribute(name); - return; - } - } - if (__DEV__) { - checkAttributeStringCoercion(value, name); - } - node.setAttribute( - name, - enableTrustedTypesIntegration ? (value: any) : '' + (value: any), - ); -} - export function setValueForNamespacedAttribute( node: Element, namespace: string, diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 20395d09ac090..60776996e537a 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -22,7 +22,6 @@ import { getValueForAttribute, getValueForAttributeOnCustomComponent, setValueForPropertyOnCustomComponent, - setValueForKnownAttribute, setValueForAttribute, setValueForNamespacedAttribute, } from './DOMPropertyOperations'; @@ -59,7 +58,6 @@ import { } from './CSSPropertyOperations'; import {HTML_NAMESPACE, getIntrinsicNamespace} 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'; @@ -277,6 +275,35 @@ function setProp( props: any, ): void { switch (key) { + case 'style': { + setValueForStyles(domElement, value); + break; + } + 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://reactjs.org/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`.', + ); + } + if (disableIEWorkarounds) { + domElement.innerHTML = nextHtml; + } else { + setInnerHTML(domElement, nextHtml); + } + } + } + break; + } case 'children': { if (typeof value === 'string') { // Avoid setting initial textContent when the text is empty. In IE11 setting @@ -297,26 +324,50 @@ function setProp( } break; } - // These are very common props and therefore are in the beginning of the switch. - // TODO: aria-label is a very common prop but allows booleans so is not like the others - // but should ideally go in this list too. - case 'className': - setValueForKnownAttribute(domElement, 'class', value); + case 'onScroll': { + if (value != null) { + if (__DEV__ && typeof value !== 'function') { + warnForInvalidEventListener(key, value); + } + listenToNonDelegatedEvent('scroll', domElement); + } break; - case 'tabIndex': - // This has to be case sensitive in SVG. - setValueForKnownAttribute(domElement, 'tabindex', value); + } + case 'onClick': { + // TODO: This cast may not be sound for SVG, MathML or custom elements. + if (value != null) { + if (__DEV__ && typeof value !== 'function') { + warnForInvalidEventListener(key, value); + } + trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); + } + break; + } + // Note: `option.selected` is not updated if `select.multiple` is + // disabled with `removeAttribute`. We have special logic for handling this. + case 'multiple': { + (domElement: any).multiple = + value && typeof value !== 'function' && typeof value !== 'symbol'; break; - case 'dir': - case 'role': - case 'viewBox': - case 'width': - case 'height': { - setValueForKnownAttribute(domElement, key, value); + } + case 'muted': { + (domElement: any).muted = + value && typeof value !== 'function' && typeof value !== 'symbol'; break; } - case 'style': { - setValueForStyles(domElement, value); + case 'suppressContentEditableWarning': + case 'suppressHydrationWarning': + case 'defaultValue': // Reserved + case 'defaultChecked': + case 'innerHTML': { + // Noop + break; + } + case 'autoFocus': { + // We polyfill it separately on the client during commit. + // We could have excluded it in the property list instead of + // adding a special case here, but then it wouldn't be emitted + // on server rendering (but we *do* want to emit it in SSR). break; } // These attributes accept URLs. These must not allow javascript: URLS. @@ -372,77 +423,6 @@ function setProp( domElement.setAttribute(key, sanitizedValue); break; } - case 'onClick': { - // TODO: This cast may not be sound for SVG, MathML or custom elements. - if (value != null) { - if (__DEV__ && typeof value !== 'function') { - warnForInvalidEventListener(key, value); - } - trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); - } - break; - } - case 'onScroll': { - if (value != null) { - if (__DEV__ && typeof value !== 'function') { - warnForInvalidEventListener(key, value); - } - listenToNonDelegatedEvent('scroll', domElement); - } - break; - } - 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://reactjs.org/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`.', - ); - } - if (disableIEWorkarounds) { - domElement.innerHTML = nextHtml; - } else { - setInnerHTML(domElement, nextHtml); - } - } - } - break; - } - // Note: `option.selected` is not updated if `select.multiple` is - // disabled with `removeAttribute`. We have special logic for handling this. - 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': // Reserved - case 'defaultChecked': - case 'innerHTML': { - // Noop - break; - } - case 'autoFocus': { - // We polyfill it separately on the client during commit. - // We could have excluded it in the property list instead of - // adding a special case here, but then it wouldn't be emitted - // on server rendering (but we *do* want to emit it in SSR). - break; - } case 'xlinkHref': { if ( value == null || @@ -585,6 +565,251 @@ function setProp( } break; } + // A few React string attributes have a different name. + // This is a mapping from React prop names to the attribute names. + case 'acceptCharset': + setValueForAttribute(domElement, 'accept-charset', value); + break; + case 'className': + setValueForAttribute(domElement, 'class', value); + break; + case 'htmlFor': + setValueForAttribute(domElement, 'for', value); + break; + case 'httpEquiv': + setValueForAttribute(domElement, 'http-equiv', value); + break; + // HTML and SVG attributes, but the SVG attribute is case sensitive. + case 'tabIndex': + setValueForAttribute(domElement, 'tabindex', value); + break; + case 'crossOrigin': + setValueForAttribute(domElement, 'crossorigin', value); + break; + // This is a list of all SVG attributes that need special casing. + // Regular attributes that just accept strings. + case 'accentHeight': + setValueForAttribute(domElement, 'accent-height', value); + break; + case 'alignmentBaseline': + setValueForAttribute(domElement, 'alignment-baseline', value); + break; + case 'arabicForm': + setValueForAttribute(domElement, 'arabic-form', value); + break; + case 'baselineShift': + setValueForAttribute(domElement, 'baseline-shift', value); + break; + case 'capHeight': + setValueForAttribute(domElement, 'cap-height', value); + break; + case 'clipPath': + setValueForAttribute(domElement, 'clip-path', value); + break; + case 'clipRule': + setValueForAttribute(domElement, 'clip-rule', value); + break; + case 'colorInterpolation': + setValueForAttribute(domElement, 'color-interpolation', value); + break; + case 'colorInterpolationFilters': + setValueForAttribute(domElement, 'color-interpolation-filters', value); + break; + case 'colorProfile': + setValueForAttribute(domElement, 'color-profile', value); + break; + case 'colorRendering': + setValueForAttribute(domElement, 'color-rendering', value); + break; + case 'dominantBaseline': + setValueForAttribute(domElement, 'dominant-baseline', value); + break; + case 'enableBackground': + setValueForAttribute(domElement, 'enable-background', value); + break; + case 'fillOpacity': + setValueForAttribute(domElement, 'fill-opacity', value); + break; + case 'fillRule': + setValueForAttribute(domElement, 'fill-rule', value); + break; + case 'floodColor': + setValueForAttribute(domElement, 'flood-color', value); + break; + case 'floodOpacity': + setValueForAttribute(domElement, 'flood-opacity', value); + break; + case 'fontFamily': + setValueForAttribute(domElement, 'font-family', value); + break; + case 'fontSize': + setValueForAttribute(domElement, 'font-size', value); + break; + case 'fontSizeAdjust': + setValueForAttribute(domElement, 'font-size-adjust', value); + break; + case 'fontStretch': + setValueForAttribute(domElement, 'font-stretch', value); + break; + case 'fontStyle': + setValueForAttribute(domElement, 'font-style', value); + break; + case 'fontVariant': + setValueForAttribute(domElement, 'font-variant', value); + break; + case 'fontWeight': + setValueForAttribute(domElement, 'font-weight', value); + break; + case 'glyphName': + setValueForAttribute(domElement, 'glyph-name', value); + break; + case 'glyphOrientationHorizontal': + setValueForAttribute(domElement, 'glyph-orientation-horizontal', value); + break; + case 'glyphOrientationVertical': + setValueForAttribute(domElement, 'glyph-orientation-vertical', value); + break; + case 'horizAdvX': + setValueForAttribute(domElement, 'horiz-adv-x', value); + break; + case 'horizOriginX': + setValueForAttribute(domElement, 'horiz-origin-x', value); + break; + case 'imageRendering': + setValueForAttribute(domElement, 'image-rendering', value); + break; + case 'letterSpacing': + setValueForAttribute(domElement, 'letter-spacing', value); + break; + case 'lightingColor': + setValueForAttribute(domElement, 'lighting-color', value); + break; + case 'markerEnd': + setValueForAttribute(domElement, 'marker-end', value); + break; + case 'markerMid': + setValueForAttribute(domElement, 'marker-mid', value); + break; + case 'markerStart': + setValueForAttribute(domElement, 'marker-start', value); + break; + case 'overlinePosition': + setValueForAttribute(domElement, 'overline-position', value); + break; + case 'overlineThickness': + setValueForAttribute(domElement, 'overline-thickness', value); + break; + case 'paintOrder': + setValueForAttribute(domElement, 'paint-order', value); + break; + case 'panose-1': + setValueForAttribute(domElement, 'panose-1', value); + break; + case 'pointerEvents': + setValueForAttribute(domElement, 'pointer-events', value); + break; + case 'renderingIntent': + setValueForAttribute(domElement, 'rendering-intent', value); + break; + case 'shapeRendering': + setValueForAttribute(domElement, 'shape-rendering', value); + break; + case 'stopColor': + setValueForAttribute(domElement, 'stop-color', value); + break; + case 'stopOpacity': + setValueForAttribute(domElement, 'stop-opacity', value); + break; + case 'strikethroughPosition': + setValueForAttribute(domElement, 'strikethrough-position', value); + break; + case 'strikethroughThickness': + setValueForAttribute(domElement, 'strikethrough-thickness', value); + break; + case 'strokeDasharray': + setValueForAttribute(domElement, 'stroke-dasharray', value); + break; + case 'strokeDashoffset': + setValueForAttribute(domElement, 'stroke-dashoffset', value); + break; + case 'strokeLinecap': + setValueForAttribute(domElement, 'stroke-linecap', value); + break; + case 'strokeLinejoin': + setValueForAttribute(domElement, 'stroke-linejoin', value); + break; + case 'strokeMiterlimit': + setValueForAttribute(domElement, 'stroke-miterlimit', value); + break; + case 'strokeOpacity': + setValueForAttribute(domElement, 'stroke-opacity', value); + break; + case 'strokeWidth': + setValueForAttribute(domElement, 'stroke-width', value); + break; + case 'textAnchor': + setValueForAttribute(domElement, 'text-anchor', value); + break; + case 'textDecoration': + setValueForAttribute(domElement, 'text-decoration', value); + break; + case 'textRendering': + setValueForAttribute(domElement, 'text-rendering', value); + break; + case 'transformOrigin': + setValueForAttribute(domElement, 'transform-origin', value); + break; + case 'underlinePosition': + setValueForAttribute(domElement, 'underline-position', value); + break; + case 'underlineThickness': + setValueForAttribute(domElement, 'underline-thickness', value); + break; + case 'unicodeBidi': + setValueForAttribute(domElement, 'unicode-bidi', value); + break; + case 'unicodeRange': + setValueForAttribute(domElement, 'unicode-range', value); + break; + case 'unitsPerEm': + setValueForAttribute(domElement, 'units-per-em', value); + break; + case 'vAlphabetic': + setValueForAttribute(domElement, 'v-alphabetic', value); + break; + case 'vHanging': + setValueForAttribute(domElement, 'v-hanging', value); + break; + case 'vIdeographic': + setValueForAttribute(domElement, 'v-ideographic', value); + break; + case 'vMathematical': + setValueForAttribute(domElement, 'v-mathematical', value); + break; + case 'vectorEffect': + setValueForAttribute(domElement, 'vector-effect', value); + break; + case 'vertAdvY': + setValueForAttribute(domElement, 'vert-adv-y', value); + break; + case 'vertOriginX': + setValueForAttribute(domElement, 'vert-origin-x', value); + break; + case 'vertOriginY': + setValueForAttribute(domElement, 'vert-origin-y', value); + break; + case 'wordSpacing': + setValueForAttribute(domElement, 'word-spacing', value); + break; + case 'writingMode': + setValueForAttribute(domElement, 'writing-mode', value); + break; + case 'xmlnsXlink': + setValueForAttribute(domElement, 'xmlns:xlink', value); + break; + case 'xHeight': + setValueForAttribute(domElement, 'x-height', value); + break; case 'xlinkActuate': setValueForNamespacedAttribute( domElement, @@ -679,8 +904,7 @@ function setProp( warnForInvalidEventListener(key, value); } } else { - const attributeName = getAttributeAlias(key); - setValueForAttribute(domElement, attributeName, value); + setValueForAttribute(domElement, key, value); } } } @@ -794,17 +1018,6 @@ export function setInitialProperties( // TODO: Make sure that we check isMounted before firing any of these events. switch (tag) { - case 'div': - case 'span': - case 'svg': - case 'path': - case 'a': - case 'g': - case 'p': - case 'li': { - // Fast track the most common tag types - break; - } case 'input': { ReactDOMInputInitWrapperState(domElement, props); // We listen to this event in case to ensure emulated bubble @@ -819,21 +1032,6 @@ export function setInitialProperties( continue; } switch (propKey) { - case 'type': { - // Fast path since 'type' is very common on inputs - if ( - propValue != null && - typeof propValue !== 'function' && - typeof propValue !== 'symbol' && - typeof propValue !== 'boolean' - ) { - if (__DEV__) { - checkAttributeStringCoercion(propValue, propKey); - } - domElement.setAttribute(propKey, propValue); - } - break; - } case 'checked': { const node = ((domElement: any): InputWithWrapperState); const checked = @@ -1048,32 +1246,30 @@ export function setInitialProperties( } return; } - default: { - if (isCustomElement(tag, props)) { - for (const propKey in props) { - if (!props.hasOwnProperty(propKey)) { - continue; - } - const propValue = props[propKey]; - if (propValue == null) { - continue; - } - setPropOnCustomElement(domElement, tag, propKey, propValue, props); - } - return; - } - } } - for (const propKey in props) { - if (!props.hasOwnProperty(propKey)) { - continue; + if (isCustomElement(tag, props)) { + for (const propKey in props) { + if (!props.hasOwnProperty(propKey)) { + continue; + } + const propValue = props[propKey]; + if (propValue == null) { + continue; + } + setPropOnCustomElement(domElement, tag, propKey, propValue, props); } - const propValue = props[propKey]; - if (propValue == null) { - continue; + } else { + for (const propKey in props) { + if (!props.hasOwnProperty(propKey)) { + continue; + } + const propValue = props[propKey]; + if (propValue == null) { + continue; + } + setProp(domElement, tag, propKey, propValue, props); } - setProp(domElement, tag, propKey, propValue, props); } } @@ -1199,17 +1395,6 @@ export function updateProperties( nextProps: Object, ): void { switch (tag) { - case 'div': - case 'span': - case 'svg': - case 'path': - case 'a': - case 'g': - case 'p': - case 'li': { - // Fast track the most common tag types - break; - } case 'input': { // Update checked *before* name. // In the middle of an update, it is possible to have multiple checked. @@ -1367,29 +1552,21 @@ export function updateProperties( } return; } - default: { - if (isCustomElement(tag, nextProps)) { - for (let i = 0; i < updatePayload.length; i += 2) { - const propKey = updatePayload[i]; - const propValue = updatePayload[i + 1]; - setPropOnCustomElement( - domElement, - tag, - propKey, - propValue, - nextProps, - ); - } - return; - } - } } // Apply the diff. - for (let i = 0; i < updatePayload.length; i += 2) { - const propKey = updatePayload[i]; - const propValue = updatePayload[i + 1]; - setProp(domElement, tag, propKey, propValue, nextProps); + if (isCustomElement(tag, nextProps)) { + for (let i = 0; i < updatePayload.length; i += 2) { + const propKey = updatePayload[i]; + const propValue = updatePayload[i + 1]; + setPropOnCustomElement(domElement, tag, propKey, propValue, nextProps); + } + } else { + for (let i = 0; i < updatePayload.length; i += 2) { + const propKey = updatePayload[i]; + const propValue = updatePayload[i + 1]; + setProp(domElement, tag, propKey, propValue, nextProps); + } } } @@ -1871,18 +2048,6 @@ function diffHydratedGenericElement( warnForPropDifference(propKey, serverHTML, expectedHTML); } continue; - case 'className': - hydrateAttribute(domElement, propKey, 'class', value, extraAttributes); - continue; - case 'tabIndex': - hydrateAttribute( - domElement, - propKey, - 'tabindex', - value, - extraAttributes, - ); - continue; case 'style': extraAttributes.delete(propKey); diffHydratedStyles(domElement, value); @@ -2079,132 +2244,828 @@ function diffHydratedGenericElement( ); continue; } - case 'xHeight': + // A few React string attributes have a different name. + // This is a mapping from React prop names to the attribute names. + case 'acceptCharset': hydrateAttribute( domElement, propKey, - 'x-height', + 'accept-charset', value, extraAttributes, ); continue; - case 'xlinkActuate': + case 'className': + hydrateAttribute(domElement, propKey, 'class', value, extraAttributes); + continue; + case 'htmlFor': + hydrateAttribute(domElement, propKey, 'for', value, extraAttributes); + continue; + case 'httpEquiv': hydrateAttribute( domElement, propKey, - 'xlink:actuate', + 'http-equiv', value, extraAttributes, ); continue; - case 'xlinkArcrole': + case 'tabIndex': hydrateAttribute( domElement, propKey, - 'xlink:arcrole', + 'tabindex', value, extraAttributes, ); continue; - case 'xlinkRole': + case 'crossOrigin': hydrateAttribute( domElement, propKey, - 'xlink:role', + 'crossorigin', value, extraAttributes, ); continue; - case 'xlinkShow': + case 'accentHeight': hydrateAttribute( domElement, propKey, - 'xlink:show', + 'accent-height', value, extraAttributes, ); continue; - case 'xlinkTitle': + case 'alignmentBaseline': hydrateAttribute( domElement, propKey, - 'xlink:title', + 'alignment-baseline', value, extraAttributes, ); continue; - case 'xlinkType': + case 'arabicForm': hydrateAttribute( domElement, propKey, - 'xlink:type', + 'arabic-form', value, extraAttributes, ); continue; - case 'xmlBase': + case 'baselineShift': hydrateAttribute( domElement, propKey, - 'xml:base', + 'baseline-shift', value, extraAttributes, ); continue; - case 'xmlLang': + case 'capHeight': hydrateAttribute( domElement, propKey, - 'xml:lang', + 'cap-height', value, extraAttributes, ); continue; - case 'xmlSpace': + case 'clipPath': hydrateAttribute( domElement, propKey, - 'xml:space', + 'clip-path', value, extraAttributes, ); continue; - default: { - if ( - // shouldIgnoreAttribute - // We have already filtered out null/undefined and reserved words. - propKey.length > 2 && - (propKey[0] === 'o' || propKey[0] === 'O') && - (propKey[1] === 'n' || propKey[1] === 'N') - ) { - continue; - } - const attributeName = getAttributeAlias(propKey); - let isMismatchDueToBadCasing = false; - let ownNamespaceDev = parentNamespaceDev; - if (ownNamespaceDev === HTML_NAMESPACE) { - ownNamespaceDev = getIntrinsicNamespace(tag); - } - if (ownNamespaceDev === HTML_NAMESPACE) { - extraAttributes.delete(attributeName.toLowerCase()); - } else { - const standardName = getPossibleStandardName(propKey); - if (standardName !== null && standardName !== propKey) { - // If an SVG prop is supplied with bad casing, it will - // be successfully parsed from HTML, but will produce a mismatch - // (and would be incorrectly rendered on the client). - // However, we already warn about bad casing elsewhere. - // So we'll skip the misleading extra mismatch warning in this case. - isMismatchDueToBadCasing = true; - extraAttributes.delete(standardName); - } - extraAttributes.delete(attributeName); - } - const serverValue = getValueForAttribute( + case 'clipRule': + hydrateAttribute( + domElement, + propKey, + 'clip-rule', + value, + extraAttributes, + ); + continue; + case 'colorInterpolation': + hydrateAttribute( + domElement, + propKey, + 'color-interpolation', + value, + extraAttributes, + ); + continue; + case 'colorInterpolationFilters': + hydrateAttribute( + domElement, + propKey, + 'color-interpolation-filters', + value, + extraAttributes, + ); + continue; + case 'colorProfile': + hydrateAttribute( + domElement, + propKey, + 'color-profile', + value, + extraAttributes, + ); + continue; + case 'colorRendering': + hydrateAttribute( + domElement, + propKey, + 'color-rendering', + value, + extraAttributes, + ); + continue; + case 'dominantBaseline': + hydrateAttribute( + domElement, + propKey, + 'dominant-baseline', + value, + extraAttributes, + ); + continue; + case 'enableBackground': + hydrateAttribute( + domElement, + propKey, + 'enable-background', + value, + extraAttributes, + ); + continue; + case 'fillOpacity': + hydrateAttribute( + domElement, + propKey, + 'fill-opacity', + value, + extraAttributes, + ); + continue; + case 'fillRule': + hydrateAttribute( + domElement, + propKey, + 'fill-rule', + value, + extraAttributes, + ); + continue; + case 'floodColor': + hydrateAttribute( + domElement, + propKey, + 'flood-color', + value, + extraAttributes, + ); + continue; + case 'floodOpacity': + hydrateAttribute( + domElement, + propKey, + 'flood-opacity', + value, + extraAttributes, + ); + continue; + case 'fontFamily': + hydrateAttribute( + domElement, + propKey, + 'font-family', + value, + extraAttributes, + ); + continue; + case 'fontSize': + hydrateAttribute( + domElement, + propKey, + 'font-size', + value, + extraAttributes, + ); + continue; + case 'fontSizeAdjust': + hydrateAttribute( + domElement, + propKey, + 'font-size-adjust', + value, + extraAttributes, + ); + continue; + case 'fontStretch': + hydrateAttribute( + domElement, + propKey, + 'font-stretch', + value, + extraAttributes, + ); + continue; + case 'fontStyle': + hydrateAttribute( + domElement, + propKey, + 'font-style', + value, + extraAttributes, + ); + continue; + case 'fontVariant': + hydrateAttribute( + domElement, + propKey, + 'font-variant', + value, + extraAttributes, + ); + continue; + case 'fontWeight': + hydrateAttribute( + domElement, + propKey, + 'font-weight', + value, + extraAttributes, + ); + continue; + case 'glyphName': + hydrateAttribute( + domElement, + propKey, + 'glyph-name', + value, + extraAttributes, + ); + continue; + case 'glyphOrientationHorizontal': + hydrateAttribute( + domElement, + propKey, + 'glyph-orientation-horizontal', + value, + extraAttributes, + ); + continue; + case 'glyphOrientationVertical': + hydrateAttribute( + domElement, + propKey, + 'glyph-orientation-vertical', + value, + extraAttributes, + ); + continue; + case 'horizAdvX': + hydrateAttribute( + domElement, + propKey, + 'horiz-adv-x', + value, + extraAttributes, + ); + continue; + case 'horizOriginX': + hydrateAttribute( + domElement, + propKey, + 'horiz-origin-x', + value, + extraAttributes, + ); + continue; + case 'imageRendering': + hydrateAttribute( + domElement, + propKey, + 'image-rendering', + value, + extraAttributes, + ); + continue; + case 'letterSpacing': + hydrateAttribute( + domElement, + propKey, + 'letter-spacing', + value, + extraAttributes, + ); + continue; + case 'lightingColor': + hydrateAttribute( domElement, - attributeName, + propKey, + 'lighting-color', value, + extraAttributes, ); + continue; + case 'markerEnd': + hydrateAttribute( + domElement, + propKey, + 'marker-end', + value, + extraAttributes, + ); + continue; + case 'markerMid': + hydrateAttribute( + domElement, + propKey, + 'marker-mid', + value, + extraAttributes, + ); + continue; + case 'markerStart': + hydrateAttribute( + domElement, + propKey, + 'marker-start', + value, + extraAttributes, + ); + continue; + case 'overlinePosition': + hydrateAttribute( + domElement, + propKey, + 'overline-position', + value, + extraAttributes, + ); + continue; + case 'overlineThickness': + hydrateAttribute( + domElement, + propKey, + 'overline-thickness', + value, + extraAttributes, + ); + continue; + case 'paintOrder': + hydrateAttribute( + domElement, + propKey, + 'paint-order', + value, + extraAttributes, + ); + continue; + case 'panose-1': + hydrateAttribute( + domElement, + propKey, + 'panose-1', + value, + extraAttributes, + ); + continue; + case 'pointerEvents': + hydrateAttribute( + domElement, + propKey, + 'pointer-events', + value, + extraAttributes, + ); + continue; + case 'renderingIntent': + hydrateAttribute( + domElement, + propKey, + 'rendering-intent', + value, + extraAttributes, + ); + continue; + case 'shapeRendering': + hydrateAttribute( + domElement, + propKey, + 'shape-rendering', + value, + extraAttributes, + ); + continue; + case 'stopColor': + hydrateAttribute( + domElement, + propKey, + 'stop-color', + value, + extraAttributes, + ); + continue; + case 'stopOpacity': + hydrateAttribute( + domElement, + propKey, + 'stop-opacity', + value, + extraAttributes, + ); + continue; + case 'strikethroughPosition': + hydrateAttribute( + domElement, + propKey, + 'strikethrough-position', + value, + extraAttributes, + ); + continue; + case 'strikethroughThickness': + hydrateAttribute( + domElement, + propKey, + 'strikethrough-thickness', + value, + extraAttributes, + ); + continue; + case 'strokeDasharray': + hydrateAttribute( + domElement, + propKey, + 'stroke-dasharray', + value, + extraAttributes, + ); + continue; + case 'strokeDashoffset': + hydrateAttribute( + domElement, + propKey, + 'stroke-dashoffset', + value, + extraAttributes, + ); + continue; + case 'strokeLinecap': + hydrateAttribute( + domElement, + propKey, + 'stroke-linecap', + value, + extraAttributes, + ); + continue; + case 'strokeLinejoin': + hydrateAttribute( + domElement, + propKey, + 'stroke-linejoin', + value, + extraAttributes, + ); + continue; + case 'strokeMiterlimit': + hydrateAttribute( + domElement, + propKey, + 'stroke-miterlimit', + value, + extraAttributes, + ); + continue; + case 'strokeOpacity': + hydrateAttribute( + domElement, + propKey, + 'stroke-opacity', + value, + extraAttributes, + ); + continue; + case 'strokeWidth': + hydrateAttribute( + domElement, + propKey, + 'stroke-width', + value, + extraAttributes, + ); + continue; + case 'textAnchor': + hydrateAttribute( + domElement, + propKey, + 'text-anchor', + value, + extraAttributes, + ); + continue; + case 'textDecoration': + hydrateAttribute( + domElement, + propKey, + 'text-decoration', + value, + extraAttributes, + ); + continue; + case 'textRendering': + hydrateAttribute( + domElement, + propKey, + 'text-rendering', + value, + extraAttributes, + ); + continue; + case 'transformOrigin': + hydrateAttribute( + domElement, + propKey, + 'transform-origin', + value, + extraAttributes, + ); + continue; + case 'underlinePosition': + hydrateAttribute( + domElement, + propKey, + 'underline-position', + value, + extraAttributes, + ); + continue; + case 'underlineThickness': + hydrateAttribute( + domElement, + propKey, + 'underline-thickness', + value, + extraAttributes, + ); + continue; + case 'unicodeBidi': + hydrateAttribute( + domElement, + propKey, + 'unicode-bidi', + value, + extraAttributes, + ); + continue; + case 'unicodeRange': + hydrateAttribute( + domElement, + propKey, + 'unicode-range', + value, + extraAttributes, + ); + continue; + case 'unitsPerEm': + hydrateAttribute( + domElement, + propKey, + 'units-per-em', + value, + extraAttributes, + ); + continue; + case 'vAlphabetic': + hydrateAttribute( + domElement, + propKey, + 'v-alphabetic', + value, + extraAttributes, + ); + continue; + case 'vHanging': + hydrateAttribute( + domElement, + propKey, + 'v-hanging', + value, + extraAttributes, + ); + continue; + case 'vIdeographic': + hydrateAttribute( + domElement, + propKey, + 'v-ideographic', + value, + extraAttributes, + ); + continue; + case 'vMathematical': + hydrateAttribute( + domElement, + propKey, + 'v-mathematical', + value, + extraAttributes, + ); + continue; + case 'vectorEffect': + hydrateAttribute( + domElement, + propKey, + 'vector-effect', + value, + extraAttributes, + ); + continue; + case 'vertAdvY': + hydrateAttribute( + domElement, + propKey, + 'vert-adv-y', + value, + extraAttributes, + ); + continue; + case 'vertOriginX': + hydrateAttribute( + domElement, + propKey, + 'vert-origin-x', + value, + extraAttributes, + ); + continue; + case 'vertOriginY': + hydrateAttribute( + domElement, + propKey, + 'vert-origin-y', + value, + extraAttributes, + ); + continue; + case 'wordSpacing': + hydrateAttribute( + domElement, + propKey, + 'word-spacing', + value, + extraAttributes, + ); + continue; + case 'writingMode': + hydrateAttribute( + domElement, + propKey, + 'writing-mode', + value, + extraAttributes, + ); + continue; + case 'xmlnsXlink': + hydrateAttribute( + domElement, + propKey, + 'xmlns:xlink', + value, + extraAttributes, + ); + continue; + case 'xHeight': + hydrateAttribute( + domElement, + propKey, + 'x-height', + value, + extraAttributes, + ); + continue; + case 'xlinkActuate': + hydrateAttribute( + domElement, + propKey, + 'xlink:actuate', + value, + extraAttributes, + ); + continue; + case 'xlinkArcrole': + hydrateAttribute( + domElement, + propKey, + 'xlink:arcrole', + value, + extraAttributes, + ); + continue; + case 'xlinkRole': + hydrateAttribute( + domElement, + propKey, + 'xlink:role', + value, + extraAttributes, + ); + continue; + case 'xlinkShow': + hydrateAttribute( + domElement, + propKey, + 'xlink:show', + value, + extraAttributes, + ); + continue; + case 'xlinkTitle': + hydrateAttribute( + domElement, + propKey, + 'xlink:title', + value, + extraAttributes, + ); + continue; + case 'xlinkType': + hydrateAttribute( + domElement, + propKey, + 'xlink:type', + value, + extraAttributes, + ); + continue; + case 'xmlBase': + hydrateAttribute( + domElement, + propKey, + 'xml:base', + value, + extraAttributes, + ); + continue; + case 'xmlLang': + hydrateAttribute( + domElement, + propKey, + 'xml:lang', + value, + extraAttributes, + ); + continue; + case 'xmlSpace': + hydrateAttribute( + domElement, + propKey, + 'xml:space', + value, + extraAttributes, + ); + continue; + default: { + if ( + // shouldIgnoreAttribute + // We have already filtered out null/undefined and reserved words. + propKey.length > 2 && + (propKey[0] === 'o' || propKey[0] === 'O') && + (propKey[1] === 'n' || propKey[1] === 'N') + ) { + continue; + } + let isMismatchDueToBadCasing = false; + let ownNamespaceDev = parentNamespaceDev; + if (ownNamespaceDev === HTML_NAMESPACE) { + ownNamespaceDev = getIntrinsicNamespace(tag); + } + if (ownNamespaceDev === HTML_NAMESPACE) { + extraAttributes.delete(propKey.toLowerCase()); + } else { + const standardName = getPossibleStandardName(propKey); + if (standardName !== null && standardName !== propKey) { + // If an SVG prop is supplied with bad casing, it will + // be successfully parsed from HTML, but will produce a mismatch + // (and would be incorrectly rendered on the client). + // However, we already warn about bad casing elsewhere. + // So we'll skip the misleading extra mismatch warning in this case. + isMismatchDueToBadCasing = true; + extraAttributes.delete(standardName); + } + extraAttributes.delete(propKey); + } + const serverValue = getValueForAttribute(domElement, propKey, value); if (!isMismatchDueToBadCasing) { warnForPropDifference(propKey, serverValue, value); } diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index 2df5fb332fa1d..601706df50157 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -40,7 +40,6 @@ import { import isAttributeNameSafe from '../shared/isAttributeNameSafe'; import isUnitlessNumber from '../shared/isUnitlessNumber'; -import getAttributeAlias from '../shared/getAttributeAlias'; import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes'; import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook'; @@ -641,29 +640,23 @@ function pushAttribute( value: string | boolean | number | Function | Object, // not null or undefined ): void { switch (name) { - // These are very common props and therefore are in the beginning of the switch. - // TODO: aria-label is a very common prop but allows booleans so is not like the others - // but should ideally go in this list too. - case 'className': { - pushStringAttribute(target, 'class', value); - break; - } - case 'tabIndex': { - pushStringAttribute(target, 'tabindex', value); - break; - } - case 'dir': - case 'role': - case 'viewBox': - case 'width': - case 'height': { - pushStringAttribute(target, name, value); - break; - } case 'style': { pushStyleAttribute(target, value); return; } + case 'defaultValue': + case 'defaultChecked': // These shouldn't be set as attributes on generic HTML elements. + case 'innerHTML': // Must use dangerouslySetInnerHTML instead. + case 'suppressContentEditableWarning': + case 'suppressHydrationWarning': + // Ignored. These are built-in to React on the client. + return; + case 'autoFocus': + case 'multiple': + case 'muted': { + pushBooleanAttribute(target, name.toLowerCase(), value); + return; + } case 'src': case 'href': case 'action': @@ -716,19 +709,6 @@ function pushAttribute( ); return; } - case 'defaultValue': - case 'defaultChecked': // These shouldn't be set as attributes on generic HTML elements. - case 'innerHTML': // Must use dangerouslySetInnerHTML instead. - case 'suppressContentEditableWarning': - case 'suppressHydrationWarning': - // Ignored. These are built-in to React on the client. - return; - case 'autoFocus': - case 'multiple': - case 'muted': { - pushBooleanAttribute(target, name.toLowerCase(), value); - return; - } case 'xlinkHref': { if ( typeof value === 'function' || @@ -866,33 +846,278 @@ function pushAttribute( } return; } + // A few React string attributes have a different name. + // This is a mapping from React prop names to the attribute names. + case 'acceptCharset': + pushStringAttribute(target, 'accept-charset', value); + return; + case 'className': + pushStringAttribute(target, 'class', value); + return; + case 'htmlFor': + pushStringAttribute(target, 'for', value); + return; + case 'httpEquiv': + pushStringAttribute(target, 'http-equiv', value); + return; + // HTML and SVG attributes, but the SVG attribute is case sensitive. + case 'tabIndex': + pushStringAttribute(target, 'tabindex', value); + return; + case 'crossOrigin': + pushStringAttribute(target, 'crossorigin', value); + return; + // This is a list of all SVG attributes that need special casing. + // Regular attributes that just accept strings. + case 'accentHeight': + pushStringAttribute(target, 'accent-height', value); + return; + case 'alignmentBaseline': + pushStringAttribute(target, 'alignment-baseline', value); + return; + case 'arabicForm': + pushStringAttribute(target, 'arabic-form', value); + return; + case 'baselineShift': + pushStringAttribute(target, 'baseline-shift', value); + return; + case 'capHeight': + pushStringAttribute(target, 'cap-height', value); + return; + case 'clipPath': + pushStringAttribute(target, 'clip-path', value); + return; + case 'clipRule': + pushStringAttribute(target, 'clip-rule', value); + return; + case 'colorInterpolation': + pushStringAttribute(target, 'color-interpolation', value); + return; + case 'colorInterpolationFilters': + pushStringAttribute(target, 'color-interpolation-filters', value); + return; + case 'colorProfile': + pushStringAttribute(target, 'color-profile', value); + return; + case 'colorRendering': + pushStringAttribute(target, 'color-rendering', value); + return; + case 'dominantBaseline': + pushStringAttribute(target, 'dominant-baseline', value); + return; + case 'enableBackground': + pushStringAttribute(target, 'enable-background', value); + return; + case 'fillOpacity': + pushStringAttribute(target, 'fill-opacity', value); + return; + case 'fillRule': + pushStringAttribute(target, 'fill-rule', value); + return; + case 'floodColor': + pushStringAttribute(target, 'flood-color', value); + return; + case 'floodOpacity': + pushStringAttribute(target, 'flood-opacity', value); + return; + case 'fontFamily': + pushStringAttribute(target, 'font-family', value); + return; + case 'fontSize': + pushStringAttribute(target, 'font-size', value); + return; + case 'fontSizeAdjust': + pushStringAttribute(target, 'font-size-adjust', value); + return; + case 'fontStretch': + pushStringAttribute(target, 'font-stretch', value); + return; + case 'fontStyle': + pushStringAttribute(target, 'font-style', value); + return; + case 'fontVariant': + pushStringAttribute(target, 'font-variant', value); + return; + case 'fontWeight': + pushStringAttribute(target, 'font-weight', value); + return; + case 'glyphName': + pushStringAttribute(target, 'glyph-name', value); + return; + case 'glyphOrientationHorizontal': + pushStringAttribute(target, 'glyph-orientation-horizontal', value); + return; + case 'glyphOrientationVertical': + pushStringAttribute(target, 'glyph-orientation-vertical', value); + return; + case 'horizAdvX': + pushStringAttribute(target, 'horiz-adv-x', value); + return; + case 'horizOriginX': + pushStringAttribute(target, 'horiz-origin-x', value); + return; + case 'imageRendering': + pushStringAttribute(target, 'image-rendering', value); + return; + case 'letterSpacing': + pushStringAttribute(target, 'letter-spacing', value); + return; + case 'lightingColor': + pushStringAttribute(target, 'lighting-color', value); + return; + case 'markerEnd': + pushStringAttribute(target, 'marker-end', value); + return; + case 'markerMid': + pushStringAttribute(target, 'marker-mid', value); + return; + case 'markerStart': + pushStringAttribute(target, 'marker-start', value); + return; + case 'overlinePosition': + pushStringAttribute(target, 'overline-position', value); + return; + case 'overlineThickness': + pushStringAttribute(target, 'overline-thickness', value); + return; + case 'paintOrder': + pushStringAttribute(target, 'paint-order', value); + return; + case 'panose-1': + pushStringAttribute(target, 'panose-1', value); + return; + case 'pointerEvents': + pushStringAttribute(target, 'pointer-events', value); + return; + case 'renderingIntent': + pushStringAttribute(target, 'rendering-intent', value); + return; + case 'shapeRendering': + pushStringAttribute(target, 'shape-rendering', value); + return; + case 'stopColor': + pushStringAttribute(target, 'stop-color', value); + return; + case 'stopOpacity': + pushStringAttribute(target, 'stop-opacity', value); + return; + case 'strikethroughPosition': + pushStringAttribute(target, 'strikethrough-position', value); + return; + case 'strikethroughThickness': + pushStringAttribute(target, 'strikethrough-thickness', value); + return; + case 'strokeDasharray': + pushStringAttribute(target, 'stroke-dasharray', value); + return; + case 'strokeDashoffset': + pushStringAttribute(target, 'stroke-dashoffset', value); + return; + case 'strokeLinecap': + pushStringAttribute(target, 'stroke-linecap', value); + return; + case 'strokeLinejoin': + pushStringAttribute(target, 'stroke-linejoin', value); + return; + case 'strokeMiterlimit': + pushStringAttribute(target, 'stroke-miterlimit', value); + return; + case 'strokeOpacity': + pushStringAttribute(target, 'stroke-opacity', value); + return; + case 'strokeWidth': + pushStringAttribute(target, 'stroke-width', value); + return; + case 'textAnchor': + pushStringAttribute(target, 'text-anchor', value); + return; + case 'textDecoration': + pushStringAttribute(target, 'text-decoration', value); + return; + case 'textRendering': + pushStringAttribute(target, 'text-rendering', value); + return; + case 'transformOrigin': + pushStringAttribute(target, 'transform-origin', value); + return; + case 'underlinePosition': + pushStringAttribute(target, 'underline-position', value); + return; + case 'underlineThickness': + pushStringAttribute(target, 'underline-thickness', value); + return; + case 'unicodeBidi': + pushStringAttribute(target, 'unicode-bidi', value); + return; + case 'unicodeRange': + pushStringAttribute(target, 'unicode-range', value); + return; + case 'unitsPerEm': + pushStringAttribute(target, 'units-per-em', value); + return; + case 'vAlphabetic': + pushStringAttribute(target, 'v-alphabetic', value); + return; + case 'vHanging': + pushStringAttribute(target, 'v-hanging', value); + return; + case 'vIdeographic': + pushStringAttribute(target, 'v-ideographic', value); + return; + case 'vMathematical': + pushStringAttribute(target, 'v-mathematical', value); + return; + case 'vectorEffect': + pushStringAttribute(target, 'vector-effect', value); + return; + case 'vertAdvY': + pushStringAttribute(target, 'vert-adv-y', value); + return; + case 'vertOriginX': + pushStringAttribute(target, 'vert-origin-x', value); + return; + case 'vertOriginY': + pushStringAttribute(target, 'vert-origin-y', value); + return; + case 'wordSpacing': + pushStringAttribute(target, 'word-spacing', value); + return; + case 'writingMode': + pushStringAttribute(target, 'writing-mode', value); + return; + case 'xmlnsXlink': + pushStringAttribute(target, 'xmlns:xlink', value); + return; + case 'xHeight': + pushStringAttribute(target, 'x-height', value); + return; case 'xlinkActuate': pushStringAttribute(target, 'xlink:actuate', value); - return; + break; case 'xlinkArcrole': pushStringAttribute(target, 'xlink:arcrole', value); - return; + break; case 'xlinkRole': pushStringAttribute(target, 'xlink:role', value); - return; + break; case 'xlinkShow': pushStringAttribute(target, 'xlink:show', value); - return; + break; case 'xlinkTitle': pushStringAttribute(target, 'xlink:title', value); - return; + break; case 'xlinkType': pushStringAttribute(target, 'xlink:type', value); - return; + break; case 'xmlBase': pushStringAttribute(target, 'xml:base', value); - return; + break; case 'xmlLang': pushStringAttribute(target, 'xml:lang', value); - return; + break; case 'xmlSpace': pushStringAttribute(target, 'xml:space', value); - return; + break; default: if ( // shouldIgnoreAttribute @@ -904,15 +1129,14 @@ function pushAttribute( return; } - const attributeName = getAttributeAlias(name); - if (isAttributeNameSafe(attributeName)) { + if (isAttributeNameSafe(name)) { // shouldRemoveAttribute switch (typeof value) { case 'function': case 'symbol': // eslint-disable-line return; case 'boolean': { - const prefix = attributeName.toLowerCase().slice(0, 5); + const prefix = name.toLowerCase().slice(0, 5); if (prefix !== 'data-' && prefix !== 'aria-') { return; } @@ -920,7 +1144,7 @@ function pushAttribute( } target.push( attributeSeparator, - stringToChunk(attributeName), + stringToChunk(name), attributeAssign, stringToChunk(escapeTextForBrowser(value)), attributeEnd, @@ -2638,16 +2862,6 @@ export function pushStartInstance( } switch (type) { - case 'div': - case 'span': - case 'svg': - case 'path': - case 'a': - case 'g': - case 'p': - case 'li': - // Fast track very common tags - break; // Special tags case 'select': return pushStartSelect(target, props); @@ -2757,14 +2971,15 @@ export function pushStartInstance( ); } default: { - if (type.indexOf('-') !== -1) { + if (type.indexOf('-') === -1) { + // Generic element + return pushStartGenericElement(target, props, type); + } else { // Custom element return pushStartCustomElement(target, props, type); } } } - // Generic element - return pushStartGenericElement(target, props, type); } const endTag1 = stringToPrecomputedChunk('