From 941c6284455bb80674ebe15d155547bfbba1bc2b Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Sun, 9 Apr 2017 19:20:08 -0700 Subject: [PATCH] Remove dependency on most react-dom internals --- performance/README.md | 7 +- src/apis/AppRegistry/renderApplication.js | 2 +- src/apis/PanResponder/index.js | 2 +- src/apis/StyleSheet/StyleSheetValidation.js | 6 +- src/apis/StyleSheet/normalizeValue.js | 44 +--- src/apis/UIManager/index.js | 4 +- .../Touchable/BoundingDimensions.js | 5 +- src/components/Touchable/Position.js | 3 +- src/modules/injectResponderEventPlugin.js | 4 +- src/modules/unitlessNumbers/index.js | 45 ++++ src/propTypes/ColorPropType.js | 2 +- src/propTypes/createStrictShapeTypeChecker.js | 4 +- src/vendor/PooledClass/index.js | 63 +++++ .../ReactPropTypeLocationNames/index.js | 20 ++ src/vendor/ReactPropTypesSecret/index.js | 10 + src/vendor/TouchHistoryMath/index.js | 122 +++++++++ src/vendor/setValueForStyles/index.js | 236 ++++++++++++++++++ 17 files changed, 517 insertions(+), 62 deletions(-) create mode 100644 src/modules/unitlessNumbers/index.js create mode 100644 src/vendor/PooledClass/index.js create mode 100644 src/vendor/ReactPropTypeLocationNames/index.js create mode 100644 src/vendor/ReactPropTypesSecret/index.js create mode 100644 src/vendor/TouchHistoryMath/index.js create mode 100644 src/vendor/setValueForStyles/index.js diff --git a/performance/README.md b/performance/README.md index d5700d9d..b716f07e 100644 --- a/performance/README.md +++ b/performance/README.md @@ -20,7 +20,7 @@ simple `View` without much of React Native's functionality. Typical render timings*: mean ± two standard deviations -| Implementation | Deep tree (ms) | Wide tree (ms) | Tweet (ms) | +| Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) | | :--- | ---: | ---: | ---: | | `css-modules` | `87.68` `±13.29` | `171.96` `±14.91` | | | `react-native-web/stylesheet@0.0.81` | `90.59` `±12.03` | `190.37` `±19.65` | | @@ -29,9 +29,10 @@ Typical render timings*: mean ± two standard deviations Other libraries | Implementation | Deep tree (ms) | Wide tree (ms) | -| `aphrodite@1.2.0` | `101.25` `±18.78` | `224.59` `±22.28` | +| :--- | ---: | ---: | +| `aphrodite@1.2.0` | `101.25` `±18.78` | `224.59` `±22.28` | | `glamor@3.0.0-1` | `143.39` `±23.05` | `275.21` `±21.10` | -| `react-jss@5.4.1 | `142.27` `±16.62` | `318.62` `±26.19` | +| `react-jss@5.4.1` | `142.27` `±16.62` | `318.62` `±26.19` | | `reactxp@0.34.3` | `221.36` `±23.35` | `472.61` `±40.86` | | `styled-components@2.0.0-7` | `301.92` `±39.43` | `647.80` `±102.1` | diff --git a/src/apis/AppRegistry/renderApplication.js b/src/apis/AppRegistry/renderApplication.js index 5db0b3c5..9db39c21 100644 --- a/src/apis/AppRegistry/renderApplication.js +++ b/src/apis/AppRegistry/renderApplication.js @@ -7,7 +7,7 @@ */ import invariant from 'fbjs/lib/invariant'; -import { render } from 'react-dom/lib/ReactMount'; +import { render } from 'react-dom'; import ReactNativeApp from './ReactNativeApp'; import StyleSheet from '../../apis/StyleSheet'; import React, { Component } from 'react'; diff --git a/src/apis/PanResponder/index.js b/src/apis/PanResponder/index.js index 73c8af92..552cedba 100644 --- a/src/apis/PanResponder/index.js +++ b/src/apis/PanResponder/index.js @@ -6,7 +6,7 @@ 'use strict'; -var TouchHistoryMath = require('react-dom/lib/TouchHistoryMath'); +var TouchHistoryMath = require('../../vendor/TouchHistoryMath'); var currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter; var currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter; diff --git a/src/apis/StyleSheet/StyleSheetValidation.js b/src/apis/StyleSheet/StyleSheetValidation.js index 5970c24e..3e330bd1 100644 --- a/src/apis/StyleSheet/StyleSheetValidation.js +++ b/src/apis/StyleSheet/StyleSheetValidation.js @@ -9,8 +9,8 @@ import { PropTypes } from 'react'; import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'; -import ReactPropTypeLocations from 'react-dom/lib/ReactPropTypeLocations'; -import ReactPropTypesSecret from 'react-dom/lib/ReactPropTypesSecret'; +import ReactPropTypeLocationNames from '../../vendor/ReactPropTypeLocationNames'; +import ReactPropTypesSecret from '../../vendor/ReactPropTypesSecret'; import TextInputStylePropTypes from '../../components/TextInput/TextInputStylePropTypes'; import TextStylePropTypes from '../../components/Text/TextStylePropTypes'; import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'; @@ -29,7 +29,7 @@ class StyleSheetValidation { style, prop, caller, - ReactPropTypeLocations.prop, + ReactPropTypeLocationNames.prop, null, ReactPropTypesSecret ); diff --git a/src/apis/StyleSheet/normalizeValue.js b/src/apis/StyleSheet/normalizeValue.js index 2a6d65bb..93edf81d 100644 --- a/src/apis/StyleSheet/normalizeValue.js +++ b/src/apis/StyleSheet/normalizeValue.js @@ -1,46 +1,4 @@ -const unitlessNumbers = { - animationIterationCount: true, - borderImageOutset: true, - borderImageSlice: true, - borderImageWidth: true, - boxFlex: true, - boxFlexGroup: true, - boxOrdinalGroup: true, - columnCount: true, - flex: true, - flexGrow: true, - flexOrder: true, - flexPositive: true, - flexShrink: true, - flexNegative: true, - fontWeight: true, - gridRow: true, - gridColumn: true, - lineClamp: true, - opacity: true, - order: true, - orphans: true, - tabSize: true, - widows: true, - zIndex: true, - zoom: true, - // SVG-related - fillOpacity: true, - floodOpacity: true, - stopOpacity: true, - strokeDasharray: true, - strokeDashoffset: true, - strokeMiterlimit: true, - strokeOpacity: true, - strokeWidth: true, - // transform types - scale: true, - scaleX: true, - scaleY: true, - scaleZ: true, - // RN properties - shadowOpacity: true -}; +import unitlessNumbers from '../../modules/unitlessNumbers'; const normalizeValue = (property, value) => { if (!unitlessNumbers[property] && typeof value === 'number') { diff --git a/src/apis/UIManager/index.js b/src/apis/UIManager/index.js index f9ffd33b..e4cf93b0 100644 --- a/src/apis/UIManager/index.js +++ b/src/apis/UIManager/index.js @@ -1,5 +1,5 @@ -import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations'; import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame'; +import setValueForStyles from '../../vendor/setValueForStyles'; const getRect = node => { const height = node.offsetHeight; @@ -66,7 +66,7 @@ const UIManager = { const value = props[prop]; switch (prop) { case 'style': { - CSSPropertyOperations.setValueForStyles(node, value, component._reactInternalInstance); + setValueForStyles(node, value, component._reactInternalInstance); break; } case 'class': diff --git a/src/components/Touchable/BoundingDimensions.js b/src/components/Touchable/BoundingDimensions.js index 24d1b8af..b1316068 100644 --- a/src/components/Touchable/BoundingDimensions.js +++ b/src/components/Touchable/BoundingDimensions.js @@ -1,12 +1,11 @@ /* eslint-disable */ + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. */ -'use strict'; - -var PooledClass = require('react-dom/lib/PooledClass'); +var PooledClass = require('../../vendor/PooledClass'); var twoArgumentPooler = PooledClass.twoArgumentPooler; diff --git a/src/components/Touchable/Position.js b/src/components/Touchable/Position.js index d9e3d19d..7cacb828 100644 --- a/src/components/Touchable/Position.js +++ b/src/components/Touchable/Position.js @@ -1,4 +1,5 @@ /* eslint-disable */ + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -6,7 +7,7 @@ 'use strict'; -var PooledClass = require('react-dom/lib/PooledClass'); +var PooledClass = require('../../vendor/PooledClass'); var twoArgumentPooler = PooledClass.twoArgumentPooler; diff --git a/src/modules/injectResponderEventPlugin.js b/src/modules/injectResponderEventPlugin.js index 3a80e9ea..4c2cd2c8 100644 --- a/src/modules/injectResponderEventPlugin.js +++ b/src/modules/injectResponderEventPlugin.js @@ -1,6 +1,6 @@ // based on https://github.com/facebook/react/pull/4303/files -import EventPluginRegistry from 'react-dom/lib/EventPluginRegistry'; +import EventPluginHub from 'react-dom/lib/EventPluginHub'; import normalizeNativeEvent from './normalizeNativeEvent'; import ResponderEventPlugin from 'react-dom/lib/ResponderEventPlugin'; import ResponderTouchHistoryStore from 'react-dom/lib/ResponderTouchHistoryStore'; @@ -49,6 +49,6 @@ ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => { originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizedEvent); }; -EventPluginRegistry.injectEventPluginsByName({ +EventPluginHub.injection.injectEventPluginsByName({ ResponderEventPlugin }); diff --git a/src/modules/unitlessNumbers/index.js b/src/modules/unitlessNumbers/index.js new file mode 100644 index 00000000..4bc53166 --- /dev/null +++ b/src/modules/unitlessNumbers/index.js @@ -0,0 +1,45 @@ +const unitlessNumbers = { + animationIterationCount: true, + borderImageOutset: true, + borderImageSlice: true, + borderImageWidth: true, + boxFlex: true, + boxFlexGroup: true, + boxOrdinalGroup: true, + columnCount: true, + flex: true, + flexGrow: true, + flexOrder: true, + flexPositive: true, + flexShrink: true, + flexNegative: true, + fontWeight: true, + gridRow: true, + gridColumn: true, + lineClamp: true, + opacity: true, + order: true, + orphans: true, + tabSize: true, + widows: true, + zIndex: true, + zoom: true, + // SVG-related + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeDasharray: true, + strokeDashoffset: true, + strokeMiterlimit: true, + strokeOpacity: true, + strokeWidth: true, + // transform types + scale: true, + scaleX: true, + scaleY: true, + scaleZ: true, + // RN properties + shadowOpacity: true +}; + +module.exports = unitlessNumbers; diff --git a/src/propTypes/ColorPropType.js b/src/propTypes/ColorPropType.js index 49bc2ced..335e6d93 100644 --- a/src/propTypes/ColorPropType.js +++ b/src/propTypes/ColorPropType.js @@ -14,7 +14,7 @@ import { PropTypes } from 'react'; var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) { var normalizeColor = require('normalize-css-color'); - var ReactPropTypeLocationNames = require('react-dom/lib/ReactPropTypeLocationNames'); + var ReactPropTypeLocationNames = require('../vendor/ReactPropTypeLocationNames'); var color = props[propName]; if (color === undefined || color === null) { if (isRequired) { diff --git a/src/propTypes/createStrictShapeTypeChecker.js b/src/propTypes/createStrictShapeTypeChecker.js index 1c9ee81e..f7050fce 100644 --- a/src/propTypes/createStrictShapeTypeChecker.js +++ b/src/propTypes/createStrictShapeTypeChecker.js @@ -12,8 +12,8 @@ */ import invariant from 'fbjs/lib/invariant'; -import ReactPropTypeLocationNames from 'react-dom/lib/ReactPropTypeLocationNames'; -import ReactPropTypesSecret from 'react-dom/lib/ReactPropTypesSecret'; +import ReactPropTypeLocationNames from '../vendor/ReactPropTypeLocationNames'; +import ReactPropTypesSecret from '../vendor/ReactPropTypesSecret'; function createStrictShapeTypeChecker( shapeTypes: { [key: string]: ReactPropsCheckType } diff --git a/src/vendor/PooledClass/index.js b/src/vendor/PooledClass/index.js new file mode 100644 index 00000000..cf233064 --- /dev/null +++ b/src/vendor/PooledClass/index.js @@ -0,0 +1,63 @@ +/* eslint-disable */ + +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +var invariant = require('fbjs/lib/invariant'); + +var twoArgumentPooler = function(a1, a2) { + var Klass = this; + if (Klass.instancePool.length) { + var instance = Klass.instancePool.pop(); + Klass.call(instance, a1, a2); + return instance; + } else { + return new Klass(a1, a2); + } +}; + +var standardReleaser = function(instance) { + var Klass = this; + instance.destructor(); + if (Klass.instancePool.length < Klass.poolSize) { + Klass.instancePool.push(instance); + } +}; + +var DEFAULT_POOL_SIZE = 10; +var DEFAULT_POOLER = twoArgumentPooler; + +/** + * Augments `CopyConstructor` to be a poolable class, augmenting only the class + * itself (statically) not adding any prototypical fields. Any CopyConstructor + * you give this may have a `poolSize` property, and will look for a + * prototypical `destructor` on instances. + * + * @param {Function} CopyConstructor Constructor that can be used to reset. + * @param {Function} pooler Customizable pooler. + */ +var addPoolingTo = function(CopyConstructor, pooler) { + // Casting as any so that flow ignores the actual implementation and trusts + // it to match the type we declared + var NewKlass = CopyConstructor; + NewKlass.instancePool = []; + NewKlass.getPooled = pooler || DEFAULT_POOLER; + if (!NewKlass.poolSize) { + NewKlass.poolSize = DEFAULT_POOL_SIZE; + } + NewKlass.release = standardReleaser; + return NewKlass; +}; + +var PooledClass = { + addPoolingTo: addPoolingTo, + twoArgumentPooler: twoArgumentPooler +}; + +module.exports = PooledClass; diff --git a/src/vendor/ReactPropTypeLocationNames/index.js b/src/vendor/ReactPropTypeLocationNames/index.js new file mode 100644 index 00000000..b754727e --- /dev/null +++ b/src/vendor/ReactPropTypeLocationNames/index.js @@ -0,0 +1,20 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +let ReactPropTypeLocationNames = {}; + +if (process.env.NODE_ENV !== 'production') { + ReactPropTypeLocationNames = { + prop: 'prop', + context: 'context', + childContext: 'child context' + }; +} + +module.exports = ReactPropTypeLocationNames; diff --git a/src/vendor/ReactPropTypesSecret/index.js b/src/vendor/ReactPropTypesSecret/index.js new file mode 100644 index 00000000..64c31f90 --- /dev/null +++ b/src/vendor/ReactPropTypesSecret/index.js @@ -0,0 +1,10 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; +module.exports = ReactPropTypesSecret; diff --git a/src/vendor/TouchHistoryMath/index.js b/src/vendor/TouchHistoryMath/index.js new file mode 100644 index 00000000..6966ebde --- /dev/null +++ b/src/vendor/TouchHistoryMath/index.js @@ -0,0 +1,122 @@ +/* eslint-disable */ + +var TouchHistoryMath = { + /** + * This code is optimized and not intended to look beautiful. This allows + * computing of touch centroids that have moved after `touchesChangedAfter` + * timeStamp. You can compute the current centroid involving all touches + * moves after `touchesChangedAfter`, or you can compute the previous + * centroid of all touches that were moved after `touchesChangedAfter`. + * + * @param {TouchHistoryMath} touchHistory Standard Responder touch track + * data. + * @param {number} touchesChangedAfter timeStamp after which moved touches + * are considered "actively moving" - not just "active". + * @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension. + * @param {boolean} ofCurrent Compute current centroid for actively moving + * touches vs. previous centroid of now actively moving touches. + * @return {number} value of centroid in specified dimension. + */ + centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) { + var touchBank = touchHistory.touchBank; + var total = 0; + var count = 0; + + var oneTouchData = touchHistory.numberActiveTouches === 1 + ? touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] + : null; + + if (oneTouchData !== null) { + if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) { + total += ofCurrent && isXAxis + ? oneTouchData.currentPageX + : ofCurrent && !isXAxis + ? oneTouchData.currentPageY + : !ofCurrent && isXAxis ? oneTouchData.previousPageX : oneTouchData.previousPageY; + count = 1; + } + } else { + for (var i = 0; i < touchBank.length; i++) { + var touchTrack = touchBank[i]; + if ( + touchTrack !== null && + touchTrack !== undefined && + touchTrack.touchActive && + touchTrack.currentTimeStamp >= touchesChangedAfter + ) { + var toAdd; // Yuck, program temporarily in invalid state. + if (ofCurrent && isXAxis) { + toAdd = touchTrack.currentPageX; + } else if (ofCurrent && !isXAxis) { + toAdd = touchTrack.currentPageY; + } else if (!ofCurrent && isXAxis) { + toAdd = touchTrack.previousPageX; + } else { + toAdd = touchTrack.previousPageY; + } + total += toAdd; + count++; + } + } + } + return count > 0 ? total / count : TouchHistoryMath.noCentroid; + }, + + currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + true // ofCurrent + ); + }, + + currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + true // ofCurrent + ); + }, + + previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + true, // isXAxis + false // ofCurrent + ); + }, + + previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) { + return TouchHistoryMath.centroidDimension( + touchHistory, + touchesChangedAfter, + false, // isXAxis + false // ofCurrent + ); + }, + + currentCentroidX: function(touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + true, // isXAxis + true // ofCurrent + ); + }, + + currentCentroidY: function(touchHistory) { + return TouchHistoryMath.centroidDimension( + touchHistory, + 0, // touchesChangedAfter + false, // isXAxis + true // ofCurrent + ); + }, + + noCentroid: -1 +}; + +module.exports = TouchHistoryMath; diff --git a/src/vendor/setValueForStyles/index.js b/src/vendor/setValueForStyles/index.js new file mode 100644 index 00000000..f9fdfd83 --- /dev/null +++ b/src/vendor/setValueForStyles/index.js @@ -0,0 +1,236 @@ +/* eslint-disable */ + +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +'use strict'; + +import unitlessNumbers from '../../modules/unitlessNumbers'; + +if (process.env.NODE_ENV !== 'production') { + var camelizeStyleName = require('fbjs/lib/camelizeStyleName'); + var warning = require('fbjs/lib/warning'); + + // 'msTransform' is correct, but the other prefixes should be capitalized + var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/; + + // style values shouldn't contain a semicolon + var badStyleValueWithSemicolonPattern = /;\s*$/; + + var warnedStyleNames = {}; + var warnedStyleValues = {}; + var warnedForNaNValue = false; + + var warnHyphenatedStyleName = function(name, owner) { + if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { + return; + } + + warnedStyleNames[name] = true; + process.env.NODE_ENV !== 'production' + ? warning( + false, + 'Unsupported style property %s. Did you mean %s?%s', + name, + camelizeStyleName(name), + checkRenderMessage(owner) + ) + : void 0; + }; + + var warnBadVendoredStyleName = function(name, owner) { + if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { + return; + } + + warnedStyleNames[name] = true; + process.env.NODE_ENV !== 'production' + ? warning( + false, + 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s', + name, + name.charAt(0).toUpperCase() + name.slice(1), + checkRenderMessage(owner) + ) + : void 0; + }; + + var warnStyleValueWithSemicolon = function(name, value, owner) { + if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) { + return; + } + + warnedStyleValues[value] = true; + process.env.NODE_ENV !== 'production' + ? warning( + false, + "Style property values shouldn't contain a semicolon.%s " + 'Try "%s: %s" instead.', + checkRenderMessage(owner), + name, + value.replace(badStyleValueWithSemicolonPattern, '') + ) + : void 0; + }; + + var warnStyleValueIsNaN = function(name, value, owner) { + if (warnedForNaNValue) { + return; + } + + warnedForNaNValue = true; + process.env.NODE_ENV !== 'production' + ? warning( + false, + '`NaN` is an invalid value for the `%s` css style property.%s', + name, + checkRenderMessage(owner) + ) + : void 0; + }; + + var checkRenderMessage = function(owner) { + if (owner) { + var name = owner.getName(); + if (name) { + return ' Check the render method of `' + name + '`.'; + } + } + return ''; + }; + + /** + * @param {string} name + * @param {*} value + * @param {ReactDOMComponent} component + */ + var warnValidStyle = function(name, value, component) { + var owner; + if (component) { + owner = component._currentElement._owner; + } + if (name.indexOf('-') > -1) { + warnHyphenatedStyleName(name, owner); + } else if (badVendoredStyleNamePattern.test(name)) { + warnBadVendoredStyleName(name, owner); + } else if (badStyleValueWithSemicolonPattern.test(value)) { + warnStyleValueWithSemicolon(name, value, owner); + } + + if (typeof value === 'number' && isNaN(value)) { + warnStyleValueIsNaN(name, value, owner); + } + }; +} + +var styleWarnings = {}; + +/** + * Convert a value into the proper css writable value. The style name `name` + * should be logical (no hyphens) + * + * @param {string} name CSS property name such as `topMargin`. + * @param {*} value CSS property value such as `10px`. + * @param {ReactDOMComponent} component + * @return {string} Normalized style value with dimensions applied. + */ +function dangerousStyleValue(name, value, component) { + // 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 ''; + } + + var isNonNumeric = isNaN(value); + if ( + isNonNumeric || value === 0 || (unitlessNumbers.hasOwnProperty(name) && unitlessNumbers[name]) + ) { + return '' + value; // cast to string + } + + if (typeof value === 'string') { + if (process.env.NODE_ENV !== 'production') { + var warning = require('fbjs/lib/warning'); + + // Allow '0' to pass through without warning. 0 is already special and + // doesn't require units, so we don't need to warn about it. + if (component && value !== '0') { + var owner = component._currentElement._owner; + var ownerName = owner ? owner.getName() : null; + if (ownerName && !styleWarnings[ownerName]) { + styleWarnings[ownerName] = {}; + } + var warned = false; + if (ownerName) { + var warnings = styleWarnings[ownerName]; + warned = warnings[name]; + if (!warned) { + warnings[name] = true; + } + } + if (!warned) { + process.env.NODE_ENV !== 'production' + ? warning( + false, + 'a `%s` tag (owner: `%s`) was passed a numeric string value ' + + 'for CSS property `%s` (value: `%s`) which will be treated ' + + 'as a unitless number in a future version of React.', + component._currentElement.type, + ownerName || 'unknown', + name, + value + ) + : void 0; + } + } + } + value = value.trim(); + } + return value + 'px'; +} + +/** + * Sets the value for multiple styles on a node. If a value is specified as + * '' (empty string), the corresponding style property will be unset. + * + * @param {DOMElement} node + * @param {object} styles + * @param {ReactDOMComponent} component + */ +const setValueForStyles = function(node, styles, component) { + var style = node.style; + for (var styleName in styles) { + if (!styles.hasOwnProperty(styleName)) { + continue; + } + if (process.env.NODE_ENV !== 'production') { + warnValidStyle(styleName, styles[styleName], component); + } + var styleValue = dangerousStyleValue(styleName, styles[styleName], component); + if (styleName === 'float' || styleName === 'cssFloat') { + styleName = 'cssFloat'; + } + if (styleValue) { + style[styleName] = styleValue; + } else { + style[styleName] = ''; + } + } +}; + +module.exports = setValueForStyles;