From f14dca0092ad6d97c7f7b6413cbbae365aa25e8d Mon Sep 17 00:00:00 2001 From: Leland Richardson Date: Wed, 8 Nov 2017 21:35:15 -0800 Subject: [PATCH] Support React 16 by avoiding CSSPropertyOperation import --- src/targets/react-dom.js | 140 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 4 deletions(-) diff --git a/src/targets/react-dom.js b/src/targets/react-dom.js index 4aa5664..1269601 100644 --- a/src/targets/react-dom.js +++ b/src/targets/react-dom.js @@ -10,15 +10,96 @@ */ 'use strict'; -var CSSPropertyOperations = require('react-dom/lib/CSSPropertyOperations'); var Animated = require('../'); +// Following are transform functions who accept arguments of type or . +// These functions won't work if we send them numbers, so we convert those numbers to px. +// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/transform?v=b +const transformWithLengthUnits = { + translateX: true, + translateY: true, + translateZ: true, + perspective: true, +}; + +// { translateY: 35 } => 'translateY(35px)' // { scale: 2 } => 'scale(2)' function mapTransform(t) { var k = Object.keys(t)[0]; - return `${k}(${t[k]})`; + var unit = (transformWithLengthUnits[k] && typeof t[k] === 'number') ? 'px' : ''; + return `${k}(${t[k]}${unit})`; +} + +const isUnitlessNumber = { + animationIterationCount: true, + borderImageOutset: true, + borderImageSlice: true, + borderImageWidth: true, + boxFlex: true, + boxFlexGroup: true, + boxOrdinalGroup: true, + columnCount: true, + columns: true, + flex: true, + flexGrow: true, + flexPositive: true, + flexShrink: true, + flexNegative: true, + flexOrder: true, + gridRow: true, + gridRowEnd: true, + gridRowSpan: true, + gridRowStart: true, + gridColumn: true, + gridColumnEnd: true, + gridColumnSpan: true, + gridColumnStart: true, + fontWeight: true, + lineClamp: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + tabSize: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related properties + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeDasharray: true, + strokeDashoffset: true, + strokeMiterlimit: true, + strokeOpacity: true, + strokeWidth: true, +}; + +/** + * @param {string} prefix vendor-specific prefix, eg: Webkit + * @param {string} key style name, eg: transitionDuration + * @return {string} style name prefixed with `prefix`, properly camelCased, eg: + * WebkitTransitionDuration + */ +function prefixKey(prefix, key) { + return prefix + key.charAt(0).toUpperCase() + key.substring(1); } +/** + * Support style names that may come passed in prefixed by adding permutations + * of vendor prefixes. + */ +var prefixes = ['Webkit', 'ms', 'Moz', 'O']; + +// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an +// infinite loop, because it iterates over the newly added props too. +Object.keys(isUnitlessNumber).forEach(function(prop) { + prefixes.forEach(function(prefix) { + isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; + }); +}); + // NOTE(lmr): // Since this is a hot code path, right now this is mutative... // As far as I can tell, this shouldn't cause any unexpected behavior. @@ -30,11 +111,62 @@ function mapStyle(style) { return style; } -function ApplyAnimatedValues(instance, props, comp) { +function dangerousStyleValue(name, value, isCustomProperty) { + // Note that we've removed escapeTextForBrowser() calls here since the + // whole string will be escaped when the attribute is injected into + // the markup. 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 + + var isEmpty = value == null || typeof value === 'boolean' || value === ''; + if (isEmpty) { + return ''; + } + + if ( + !isCustomProperty && + typeof value === 'number' && + value !== 0 && + !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name]) + ) { + return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers + } + + return ('' + value).trim(); +} + +function setValueForStyles(node, styles) { + var style = node.style; + for (var styleName in styles) { + if (!styles.hasOwnProperty(styleName)) { + continue; + } + var isCustomProperty = styleName.indexOf('--') === 0; + var styleValue = dangerousStyleValue( + styleName, + styles[styleName], + isCustomProperty, + ); + if (styleName === 'float') { + styleName = 'cssFloat'; + } + if (isCustomProperty) { + style.setProperty(styleName, styleValue); + } else { + style[styleName] = styleValue; + } + } +} + +function ApplyAnimatedValues(instance, props) { if (instance.setNativeProps) { instance.setNativeProps(props); } else if (instance.nodeType && instance.setAttribute !== undefined) { - CSSPropertyOperations.setValueForStyles(instance, mapStyle(props.style), comp._reactInternalInstance); + setValueForStyles(instance, mapStyle(props.style)); } else { return false; }