Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz] Implement all the DOM attributes and special cases #21153

Merged
merged 9 commits into from
Apr 1, 2021
85 changes: 83 additions & 2 deletions packages/react-dom/src/server/ReactDOMServerFormatConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ import {
NUMERIC,
POSITIVE_NUMERIC,
} from '../shared/DOMProperty';
import {isUnitlessNumber} from '../shared/CSSProperty';

import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
import warnValidStyle from '../shared/warnValidStyle';

import escapeTextForBrowser from './escapeTextForBrowser';
import hyphenateStyleName from '../shared/hyphenateStyleName';
import invariant from 'shared/invariant';
import sanitizeURL from '../shared/sanitizeURL';

Expand Down Expand Up @@ -218,18 +221,96 @@ export function pushTextInstance(
target.push(stringToChunk(encodeHTMLTextNode(text)), textSeparator);
}

const styleNameCache: Map<string, PrecomputedChunk> = new Map();
function processStyleName(styleName: string): PrecomputedChunk {
const chunk = styleNameCache.get(styleName);
if (chunk !== undefined) {
return chunk;
}
const result = stringToPrecomputedChunk(
escapeTextForBrowser(hyphenateStyleName(styleName)),
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
);
styleNameCache.set(styleName, result);
return result;
}

const styleAttributeStart = stringToPrecomputedChunk(' style="');
const styleAssign = stringToPrecomputedChunk(':');
const styleSeparator = stringToPrecomputedChunk(';');

function pushStyle(
target: Array<Chunk | PrecomputedChunk>,
responseState: ResponseState,
style: mixed,
style: Object,
): void {
invariant(
typeof style === 'object',
'The `style` prop expects a mapping from style properties to values, ' +
"not a string. For example, style={{marginRight: spacing + 'em'}} when " +
'using JSX.',
);
// TODO

target.push(styleAttributeStart);

let isFirst = true;
for (const styleName in style) {
if (!hasOwnProperty.call(style, styleName)) {
continue;
}
// If you provide unsafe user data here they can inject arbitrary CSS
// which may be problematic (I couldn't repro this):
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
// http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
// This is not an XSS hole but instead a potential CSS injection issue
// which has lead to a greater discussion about how we're going to
// trust URLs moving forward. See #2115901
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
const styleValue = style[styleName];
if (
styleValue == null ||
typeof styleValue === 'boolean' ||
styleValue === ''
) {
// TODO: We used to set empty string as a style with an empty value. Does that ever make sense?
continue;
}

let nameChunk;
let valueChunk;
const isCustomProperty = styleName.indexOf('--') === 0;
if (isCustomProperty) {
nameChunk = stringToChunk(escapeTextForBrowser(styleName));
valueChunk = stringToChunk(
escapeTextForBrowser(('' + styleValue).trim()),
);
} else {
if (__DEV__) {
warnValidStyle(styleName, styleValue);
}

nameChunk = processStyleName(styleName);
if (typeof styleValue === 'number') {
if (
styleValue !== 0 &&
!hasOwnProperty.call(isUnitlessNumber, styleName)
sebmarkbage marked this conversation as resolved.
Show resolved Hide resolved
) {
valueChunk = stringToChunk(styleValue + 'px'); // Presumes implicit 'px' suffix for unitless numbers
} else {
valueChunk = stringToChunk('' + styleValue);
}
} else {
valueChunk = stringToChunk(('' + styleValue).trim());
}
}
if (isFirst) {
isFirst = false;
// If it's first, we don't need any separators prefixed.
target.push(nameChunk, styleAssign, valueChunk);
} else {
target.push(styleSeparator, nameChunk, styleAssign, valueChunk);
}
}

target.push(attributeEnd);
}

const attributeSeparator = stringToPrecomputedChunk(' ');
Expand Down