mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
219 lines
6.7 KiB
JavaScript
219 lines
6.7 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import {
|
|
getPropertyInfo,
|
|
shouldIgnoreAttribute,
|
|
shouldRemoveAttribute,
|
|
isAttributeNameSafe,
|
|
BOOLEAN,
|
|
OVERLOADED_BOOLEAN,
|
|
} from '../shared/DOMProperty';
|
|
import sanitizeURL from '../shared/sanitizeURL';
|
|
import {
|
|
disableJavaScriptURLs,
|
|
enableTrustedTypesIntegration,
|
|
} from 'shared/ReactFeatureFlags';
|
|
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
|
|
|
|
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__) {
|
|
if (propertyInfo.mustUseProperty) {
|
|
const {propertyName} = propertyInfo;
|
|
return (node: any)[propertyName];
|
|
} else {
|
|
// This check protects multiple uses of `expected`, which is why the
|
|
// react-internal/safe-string-coercion rule is disabled in several spots
|
|
// below.
|
|
if (__DEV__) {
|
|
checkAttributeStringCoercion(expected, name);
|
|
}
|
|
|
|
if (!disableJavaScriptURLs && propertyInfo.sanitizeURL) {
|
|
// If we haven't fully disabled javascript: URLs, and if
|
|
// the hydration is successful of a javascript: URL, we
|
|
// still want to warn on the client.
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
sanitizeURL('' + (expected: any));
|
|
}
|
|
|
|
const attributeName = propertyInfo.attributeName;
|
|
|
|
let stringValue = null;
|
|
|
|
if (propertyInfo.type === OVERLOADED_BOOLEAN) {
|
|
if (node.hasAttribute(attributeName)) {
|
|
const value = node.getAttribute(attributeName);
|
|
if (value === '') {
|
|
return true;
|
|
}
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
|
|
return value;
|
|
}
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
if (value === '' + (expected: any)) {
|
|
return expected;
|
|
}
|
|
return value;
|
|
}
|
|
} else if (node.hasAttribute(attributeName)) {
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
|
|
// We had an attribute but shouldn't have had one, so read it
|
|
// for the error message.
|
|
return node.getAttribute(attributeName);
|
|
}
|
|
if (propertyInfo.type === BOOLEAN) {
|
|
// 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.
|
|
return expected;
|
|
}
|
|
// 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.
|
|
stringValue = node.getAttribute(attributeName);
|
|
}
|
|
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
|
|
return stringValue === null ? expected : stringValue;
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
} else if (stringValue === '' + (expected: any)) {
|
|
return expected;
|
|
} else {
|
|
return stringValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* attributes have multiple equivalent values.
|
|
*/
|
|
export function getValueForAttribute(
|
|
node: Element,
|
|
name: string,
|
|
expected: mixed,
|
|
): mixed {
|
|
if (__DEV__) {
|
|
if (!isAttributeNameSafe(name)) {
|
|
return;
|
|
}
|
|
if (!node.hasAttribute(name)) {
|
|
return expected === undefined ? undefined : null;
|
|
}
|
|
const value = node.getAttribute(name);
|
|
if (__DEV__) {
|
|
checkAttributeStringCoercion(expected, name);
|
|
}
|
|
if (value === '' + (expected: any)) {
|
|
return expected;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the value for a property on a node.
|
|
*
|
|
* @param {DOMElement} node
|
|
* @param {string} name
|
|
* @param {*} value
|
|
*/
|
|
export function setValueForProperty(
|
|
node: Element,
|
|
name: string,
|
|
value: mixed,
|
|
isCustomComponentTag: boolean,
|
|
) {
|
|
const propertyInfo = getPropertyInfo(name);
|
|
if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) {
|
|
return;
|
|
}
|
|
if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) {
|
|
value = null;
|
|
}
|
|
// If the prop isn't in the special list, treat it as a simple attribute.
|
|
if (isCustomComponentTag || propertyInfo === null) {
|
|
if (isAttributeNameSafe(name)) {
|
|
const attributeName = name;
|
|
if (value === null) {
|
|
node.removeAttribute(attributeName);
|
|
} else {
|
|
if (__DEV__) {
|
|
checkAttributeStringCoercion(value, name);
|
|
}
|
|
node.setAttribute(
|
|
attributeName,
|
|
enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
|
|
);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
const {mustUseProperty} = propertyInfo;
|
|
if (mustUseProperty) {
|
|
const {propertyName} = propertyInfo;
|
|
if (value === null) {
|
|
const {type} = propertyInfo;
|
|
(node: any)[propertyName] = type === BOOLEAN ? false : '';
|
|
} else {
|
|
// Contrary to `setAttribute`, object properties are properly
|
|
// `toString`ed by IE8/9.
|
|
(node: any)[propertyName] = value;
|
|
}
|
|
return;
|
|
}
|
|
// The rest are treated as attributes with special cases.
|
|
const {attributeName, attributeNamespace} = propertyInfo;
|
|
if (value === null) {
|
|
node.removeAttribute(attributeName);
|
|
} else {
|
|
const {type} = propertyInfo;
|
|
let attributeValue;
|
|
if (type === BOOLEAN || (type === OVERLOADED_BOOLEAN && value === true)) {
|
|
// If attribute type is boolean, we know for sure it won't be an execution sink
|
|
// and we won't require Trusted Type here.
|
|
attributeValue = '';
|
|
} else {
|
|
// `setAttribute` with objects becomes only `[object]` in IE8/9,
|
|
// ('' + value) makes it output the correct toString()-value.
|
|
if (enableTrustedTypesIntegration) {
|
|
attributeValue = (value: any);
|
|
} else {
|
|
if (__DEV__) {
|
|
checkAttributeStringCoercion(value, attributeName);
|
|
}
|
|
attributeValue = '' + (value: any);
|
|
}
|
|
if (propertyInfo.sanitizeURL) {
|
|
sanitizeURL(attributeValue.toString());
|
|
}
|
|
}
|
|
if (attributeNamespace) {
|
|
node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
|
|
} else {
|
|
node.setAttribute(attributeName, attributeValue);
|
|
}
|
|
}
|
|
}
|