Skip to content

Commit

Permalink
Refactor DOM Bindings Completely Off of DOMProperty Meta Programming (#…
Browse files Browse the repository at this point in the history
…26546)

This reverts commit f41ddb6.
  • Loading branch information
kassens committed Apr 20, 2023
1 parent 857df04 commit 545d874
Show file tree
Hide file tree
Showing 7 changed files with 2,696 additions and 1,099 deletions.
15 changes: 15 additions & 0 deletions packages/react-dom-bindings/src/client/CSSPropertyOperations.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ export function createDangerousStringForStyles(styles) {
* @param {object} styles
*/
export function setValueForStyles(node, styles) {
if (styles != null && typeof styles !== 'object') {
throw new Error(
'The `style` prop expects a mapping from style properties to values, ' +
"not a string. For example, style={{marginRight: spacing + 'em'}} when " +
'using JSX.',
);
}
if (__DEV__) {
if (styles) {
// Freeze the next style object so that we can assume it won't be
// mutated. We have already warned for this in the past.
Object.freeze(styles);
}
}

const style = node.style;
for (const styleName in styles) {
if (!styles.hasOwnProperty(styleName)) {
Expand Down
328 changes: 29 additions & 299 deletions packages/react-dom-bindings/src/client/DOMPropertyOperations.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,181 +7,14 @@
* @flow
*/

import {
BOOLEAN,
OVERLOADED_BOOLEAN,
NUMERIC,
POSITIVE_NUMERIC,
} from '../shared/DOMProperty';

import isAttributeNameSafe from '../shared/isAttributeNameSafe';
import sanitizeURL from '../shared/sanitizeURL';
import {
enableTrustedTypesIntegration,
enableCustomElementPropertySupport,
enableFilterEmptyStringAttributesDOM,
} from 'shared/ReactFeatureFlags';
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';

import type {PropertyInfo} from '../shared/DOMProperty';

/**
* Get the value for a property on a node. Only used in DEV for SSR validation.
* The "expected" argument is used as a hint of what the expected value is.
* Some properties have multiple equivalent values.
*/
export function getValueForProperty(
node: Element,
name: string,
expected: mixed,
propertyInfo: PropertyInfo,
): mixed {
if (__DEV__) {
const attributeName = propertyInfo.attributeName;

if (!node.hasAttribute(attributeName)) {
// shouldRemoveAttribute
switch (typeof expected) {
case 'function':
case 'symbol': // eslint-disable-line
return expected;
case 'boolean': {
if (!propertyInfo.acceptsBooleans) {
return expected;
}
}
}
switch (propertyInfo.type) {
case BOOLEAN: {
if (!expected) {
return expected;
}
break;
}
case OVERLOADED_BOOLEAN: {
if (expected === false) {
return expected;
}
break;
}
case NUMERIC: {
if (isNaN(expected)) {
return expected;
}
break;
}
case POSITIVE_NUMERIC: {
if (isNaN(expected) || (expected: any) < 1) {
return expected;
}
break;
}
}
if (enableFilterEmptyStringAttributesDOM) {
if (propertyInfo.removeEmptyString && expected === '') {
if (__DEV__) {
if (name === '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.',
name,
name,
);
} 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.',
name,
name,
);
}
}
return expected;
}
}
return expected === undefined ? undefined : null;
}

// Even if this property uses a namespace we use getAttribute
// because we assume its namespaced name is the same as our config.
// To use getAttributeNS we need the local name which we don't have
// in our config atm.
const value = node.getAttribute(attributeName);

if (expected == null) {
// We had an attribute but shouldn't have had one, so read it
// for the error message.
return value;
}

// shouldRemoveAttribute
switch (typeof expected) {
case 'function':
case 'symbol': // eslint-disable-line
return value;
}
switch (propertyInfo.type) {
case BOOLEAN: {
if (expected) {
// If this was a boolean, it doesn't matter what the value is
// the fact that we have it is the same as the expected.
// As long as it's positive.
return expected;
}
return value;
}
case OVERLOADED_BOOLEAN: {
if (value === '') {
return true;
}
if (expected === false) {
// We had an attribute but shouldn't have had one, so read it
// for the error message.
return value;
}
break;
}
case NUMERIC: {
if (isNaN(expected)) {
// We had an attribute but shouldn't have had one, so read it
// for the error message.
return value;
}
break;
}
case POSITIVE_NUMERIC: {
if (isNaN(expected) || (expected: any) < 1) {
// We had an attribute but shouldn't have had one, so read it
// for the error message.
return value;
}
break;
}
}
if (__DEV__) {
checkAttributeStringCoercion(expected, name);
}
if (propertyInfo.sanitizeURL) {
// We have already verified this above.
// eslint-disable-next-line react-internal/safe-string-coercion
if (value === '' + (sanitizeURL(expected): any)) {
return expected;
}
return value;
}
// We have already verified this above.
// eslint-disable-next-line react-internal/safe-string-coercion
if (value === '' + (expected: any)) {
return expected;
}
return value;
}
}

/**
* Get the value for a attribute on a node. Only used in DEV for SSR validation.
* The third argument is used as a hint of what the expected value is. Some
Expand Down Expand Up @@ -271,138 +104,6 @@ export function getValueForAttributeOnCustomComponent(
}
}

/**
* Sets the value for a property on a node.
*
* @param {DOMElement} node
* @param {string} name
* @param {*} value
*/
export function setValueForProperty(
node: Element,
propertyInfo: PropertyInfo,
value: mixed,
) {
const attributeName = propertyInfo.attributeName;

if (value === null) {
node.removeAttribute(attributeName);
return;
}

// shouldRemoveAttribute
switch (typeof value) {
case 'undefined':
case 'function':
case 'symbol': // eslint-disable-line
node.removeAttribute(attributeName);
return;
case 'boolean': {
if (!propertyInfo.acceptsBooleans) {
node.removeAttribute(attributeName);
return;
}
}
}
if (enableFilterEmptyStringAttributesDOM) {
if (propertyInfo.removeEmptyString && value === '') {
if (__DEV__) {
if (attributeName === '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.',
attributeName,
attributeName,
);
} 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.',
attributeName,
attributeName,
);
}
}
node.removeAttribute(attributeName);
return;
}
}

switch (propertyInfo.type) {
case BOOLEAN:
if (value) {
node.setAttribute(attributeName, '');
} else {
node.removeAttribute(attributeName);
return;
}
break;
case OVERLOADED_BOOLEAN:
if (value === true) {
node.setAttribute(attributeName, '');
} else if (value === false) {
node.removeAttribute(attributeName);
} else {
if (__DEV__) {
checkAttributeStringCoercion(value, attributeName);
}
node.setAttribute(attributeName, (value: any));
}
return;
case NUMERIC:
if (!isNaN(value)) {
if (__DEV__) {
checkAttributeStringCoercion(value, attributeName);
}
node.setAttribute(attributeName, (value: any));
} else {
node.removeAttribute(attributeName);
}
break;
case POSITIVE_NUMERIC:
if (!isNaN(value) && (value: any) >= 1) {
if (__DEV__) {
checkAttributeStringCoercion(value, attributeName);
}
node.setAttribute(attributeName, (value: any));
} else {
node.removeAttribute(attributeName);
}
break;
default: {
if (__DEV__) {
checkAttributeStringCoercion(value, attributeName);
}
let attributeValue;
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
if (enableTrustedTypesIntegration) {
if (propertyInfo.sanitizeURL) {
attributeValue = (sanitizeURL(value): any);
} else {
attributeValue = (value: any);
}
} else {
// We have already verified this above.
// eslint-disable-next-line react-internal/safe-string-coercion
attributeValue = '' + (value: any);
if (propertyInfo.sanitizeURL) {
attributeValue = sanitizeURL(attributeValue);
}
}
const attributeNamespace = propertyInfo.attributeNamespace;
if (attributeNamespace) {
node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
} else {
node.setAttribute(attributeName, attributeValue);
}
}
}
}

export function setValueForAttribute(
node: Element,
name: string,
Expand Down Expand Up @@ -439,6 +140,35 @@ export function setValueForAttribute(
}
}

export function setValueForNamespacedAttribute(
node: Element,
namespace: string,
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.setAttributeNS(
namespace,
name,
enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
);
}

export function setValueForPropertyOnCustomComponent(
node: Element,
name: string,
Expand Down
Loading

0 comments on commit 545d874

Please sign in to comment.