-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
Custom Attributes Scenario 2: Write badly cased attributes. Remove most of whitelist. #10385
Changes from 48 commits
af36bfa
601eabd
eab17b5
137af3b
55e55ba
2106844
9caa863
84beb33
f406aac
da19306
f09d3f3
aeb2db3
7cbf2f3
b67dd13
dab72d2
000c0df
a77d47b
f661d22
aa3916b
6ce3335
a11b0bd
07c6865
599844e
14d5ea0
99206db
a3c2aef
ca601c6
3e866cf
b7d6996
545bcab
afb609e
71871c4
3281320
19bfbaf
6df8b4f
cb57075
21678fc
90b4cce
fa3db62
21db719
c2c3d63
7f16b4d
3908cd3
2479fcc
8162222
e9c8910
6b1572a
cf1b0d7
b923740
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,21 @@ | |
|
||
var invariant = require('fbjs/lib/invariant'); | ||
|
||
// These attributes should be all lowercase to allow for | ||
// case insensitive checks | ||
var RESERVED_PROPS = { | ||
children: true, | ||
dangerouslysetinnerhtml: true, | ||
autofocus: true, | ||
defaultvalue: true, | ||
defaultchecked: true, | ||
innerhtml: true, | ||
suppresscontenteditablewarning: true, | ||
onfocusin: true, | ||
onfocusout: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two are currently not validated at all. If I type <div onfocusin={function() {}} /> it will silently ignore it. I would expect it to warn. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I these from I think we just need to make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works for me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll get this. |
||
style: true, | ||
}; | ||
|
||
function checkMask(value, bitmask) { | ||
return (value & bitmask) === bitmask; | ||
} | ||
|
@@ -32,11 +47,6 @@ var DOMPropertyInjection = { | |
* Inject some specialized knowledge about the DOM. This takes a config object | ||
* with the following properties: | ||
* | ||
* isCustomAttribute: function that given an attribute name will return true | ||
* if it can be inserted into the DOM verbatim. Useful for data-* or aria-* | ||
* attributes where it's impossible to enumerate all of the possible | ||
* attribute names, | ||
* | ||
* Properties: object mapping DOM property name to one of the | ||
* DOMPropertyInjection constants or null. If your attribute isn't in here, | ||
* it won't get written to the DOM. | ||
|
@@ -61,15 +71,8 @@ var DOMPropertyInjection = { | |
var Properties = domPropertyConfig.Properties || {}; | ||
var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {}; | ||
var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; | ||
var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; | ||
var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; | ||
|
||
if (domPropertyConfig.isCustomAttribute) { | ||
DOMProperty._isCustomAttributeFunctions.push( | ||
domPropertyConfig.isCustomAttribute, | ||
); | ||
} | ||
|
||
for (var propName in Properties) { | ||
invariant( | ||
!DOMProperty.properties.hasOwnProperty(propName), | ||
|
@@ -111,30 +114,28 @@ var DOMPropertyInjection = { | |
propName, | ||
); | ||
|
||
if (__DEV__) { | ||
DOMProperty.getPossibleStandardName[lowerCased] = propName; | ||
} | ||
|
||
if (DOMAttributeNames.hasOwnProperty(propName)) { | ||
var attributeName = DOMAttributeNames[propName]; | ||
|
||
propertyInfo.attributeName = attributeName; | ||
if (__DEV__) { | ||
DOMProperty.getPossibleStandardName[attributeName] = propName; | ||
} | ||
|
||
// Use the lowercase form of the attribute name to prevent | ||
// badly cased React attribute alises from writing to the DOM. | ||
DOMProperty.aliases[attributeName.toLowerCase()] = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is value always a boolean? In this case shall we name it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is always a boolean. That name change is fine with me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need a good name for the "React way" attribute names.... We call them "special React properties", "javascript form", etc.... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like to say canonical form. |
||
} | ||
|
||
if (DOMAttributeNamespaces.hasOwnProperty(propName)) { | ||
propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName]; | ||
} | ||
|
||
if (DOMPropertyNames.hasOwnProperty(propName)) { | ||
propertyInfo.propertyName = DOMPropertyNames[propName]; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DOMPropertyNames is not used by any injection. |
||
|
||
if (DOMMutationMethods.hasOwnProperty(propName)) { | ||
propertyInfo.mutationMethod = DOMMutationMethods[propName]; | ||
} | ||
|
||
// Downcase references to whitelist properties to check for membership | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where do these become lowercase? This comment confused me a bit because I expected to find |
||
// without case-sensitivity. This allows the whitelist to pick up | ||
// `allowfullscreen`, which should be written using the property configuration | ||
// for `allowFullscreen` | ||
DOMProperty.properties[propName] = propertyInfo; | ||
} | ||
}, | ||
|
@@ -197,33 +198,75 @@ var DOMProperty = { | |
properties: {}, | ||
|
||
/** | ||
* Mapping from lowercase property names to the properly cased version, used | ||
* to warn in the case of missing properties. Available only in __DEV__. | ||
* | ||
* autofocus is predefined, because adding it to the property whitelist | ||
* causes unintended side effects. | ||
* | ||
* @type {Object} | ||
* Some attributes are aliased for easier use within React. We don't | ||
* allow direct use of these attributes. See DOMAttributeNames in | ||
* HTMLPropertyConfig and SVGPropertyConfig. | ||
*/ | ||
getPossibleStandardName: __DEV__ ? {autofocus: 'autoFocus'} : null, | ||
aliases: {}, | ||
|
||
/** | ||
* All of the isCustomAttribute() functions that have been injected. | ||
*/ | ||
_isCustomAttributeFunctions: [], | ||
|
||
/** | ||
* Checks whether a property name is a custom attribute. | ||
* Checks whether a property name is a writeable attribute. | ||
* @method | ||
*/ | ||
isCustomAttribute: function(attributeName) { | ||
for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { | ||
var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; | ||
if (isCustomAttributeFn(attributeName)) { | ||
shouldSetAttribute: function(name, value) { | ||
if (DOMProperty.isReservedProp(name)) { | ||
return false; | ||
} | ||
|
||
if (value === null) { | ||
return true; | ||
} | ||
|
||
var lowerCased = name.toLowerCase(); | ||
|
||
// Prevent aliases, and badly cased aliases like `class` or `cLASS` | ||
// from showing up in the DOM | ||
if (DOMProperty.aliases.hasOwnProperty(lowerCased)) { | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we allow these but just warn? What's the rationale for hard-blocking them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it was to heavily steer users towards the alias. Dan wanted to avoid confusion where you pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I think we should allow both. For 16 we would warn, but we could explore lifting the warnings in 17/18, only warning when the alias and the standard form are included in the same prop object. For that to work, we might want to figure out which key gets priority: the alias or the native attribute name? |
||
} | ||
|
||
var propertyInfo = DOMProperty.properties[name]; | ||
|
||
switch (typeof value) { | ||
case 'boolean': | ||
if (propertyInfo) { | ||
return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sebmarkbage I'd like to figure out how much Likewise for numeric fields (which we've dropped from the whitelist, except for things like I think this might have mattered back when they were assigned as properties. I'd have to do some archeology. Dan and I made the call to keep their property info the same as 15.x, but I still can't help but wonder. |
||
} | ||
var prefix = lowerCased.slice(0, 5); | ||
return prefix === 'data-' || prefix === 'aria-'; | ||
case 'undefined': | ||
case 'number': | ||
case 'string': | ||
return true; | ||
} | ||
case 'object': | ||
// Allow HAS_BOOLEAN_VALUE to coerce to true | ||
if (propertyInfo && propertyInfo.hasBooleanValue) { | ||
return true; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we coerce objects just for boolean values? |
||
|
||
return value.toString !== Object.prototype.toString; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we dedupe this equality check with a constant towards the top of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, we should generally do a pass and inline extra the property access in this and similar functions. Let's do it after we decide the fate of this PR. |
||
default: | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We came to the conclusion that the heuristic is worse than always tostringing. So we can remove this whole thing. It'll always return true. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for taking care of this, @spicyj! |
||
} | ||
return false; | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sebmarkbage Follow up from #10416 regarding valueOf. Is it safe to evaluate stringification through Like: var stringified = '' + value
return stringified !== '[object Object]' ? stringified : null I imagine just checking for We could change this function to be more of a "getAttributeValue" function, which would return |
||
|
||
getPropertyInfo(name) { | ||
return DOMProperty.properties.hasOwnProperty(name) | ||
? DOMProperty.properties[name] | ||
: null; | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a final word here. In this scenario, all attributes are referenced as their lowercase form internally. I've hidden that within |
||
|
||
/** | ||
* Checks to see if a property name is within the list of properties | ||
* reserved for internal React operations. These properties should | ||
* not be set on an HTML element. | ||
* | ||
* @private | ||
* @param {string} name | ||
* @return {boolean} If the name is within reserved props | ||
*/ | ||
isReservedProp(name) { | ||
return RESERVED_PROPS.hasOwnProperty(name.toLowerCase()); | ||
}, | ||
|
||
injection: DOMPropertyInjection, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is slightly confusing because if I use it, it says:
But then if I try
innerHTML
, of course it isn’t supported:It seems better if the same warning was displayed immediately (or if we just got a generic "unknown property" warning for wrong casing like
innerhtml
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm working on making the warning for
onFocusIn
andonFocusOut
case insensitive. I can do that for innerHTML too.These show up in
assertValidProps
:https://github.com/facebook/react/blob/master/src/renderers/dom/shared/utils/assertValidProps.js
The easiest thing to do is move them to
ReactDOMUnknownPropertyHook
.