-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Changes to attribute whitelist logic #10564
Conversation
When we originally removed attributes from the whitelist, we assumed a few attributes were string booleans, but they are not: Autocomplete ("on", "off") https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/Attributes.html#autocomplete Autocapitalize ("none", "sentence", "words", ...) https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/Attributes.html#autocapitalize Autocorrect ("on", "off") https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/Attributes.html#autocorrect Autosave (string) https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/Attributes.html#autosave
@@ -76,21 +82,6 @@ var HTMLDOMPropertyConfig = { | |||
httpEquiv: 0, | |||
// Attributes with mutation methods must be specified in the whitelist | |||
value: 0, | |||
// The following attributes expect boolean values. They must be in | |||
// the whitelist to allow boolean attribute assignment: | |||
autoComplete: 0, |
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.
Removed autoComplete. It is actually on/off
, not true/false
. Thanks @syranide!
autoCapitalize: 0, | ||
autoCorrect: 0, | ||
// autoSave allows WebKit/Blink to persist values of input fields on page reloads | ||
autoSave: 0, |
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.
Same deal with autoSave
, autoCorrect
, and autoCapitalize
. I pulled this over from: #10531
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.
Shouldn't we special case these so that they work? It's odd when the form <x yyy />
doesn't work like the serialized HTML form.
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.
Just for context, "on"
and "off"
were deprecated for autocapitalize
in iOS5. Acceptable values 1 are "none"
, "sentences"
, "words"
, and "characters"
. When no value is given, it defaults to "sentence" for form
tags and "none" for password input
elements, but otherwise uses the attribute on the related form.
This default value appears to be an empty string, at least when I log out input.outerHTML
in Safari. It also enables capitalization, at least in a very quick check in the ios simulator.
Hmm. It is frustrating that true
is the assignment type for implicit attributes (like <input autocapitalize />
). Maybe in a breaking release of JSX there could be a symbol for it, or use an empty string.
In the mean time, should we want to parse true
as an empty string for cases like this? Maybe false
should warn. Is this behavior safe to generalize on all attributes that don't have the HAS_STRING_BOOLEAN_VALUE
flag?
@aweary is this in line what what you were thinking for boolean attributes?
Random fun aside: This is my first time using a footnote in a reply on Github. What a time to be alive.
@@ -27,6 +27,9 @@ var HTMLDOMPropertyConfig = { | |||
// name warnings. | |||
Properties: { | |||
allowFullScreen: HAS_BOOLEAN_VALUE, | |||
// IE only true/false iFrame attribute | |||
// https://msdn.microsoft.com/en-us/library/ms533072(v=vs.85).aspx | |||
allowTransparency: HAS_BOOLEAN_VALUE, |
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.
Is adding HAS_BOOLEAN_VALUE
always safe? It changes semantics (falsy get removed). Are any of these actually true
by default?
Should we keep using this flag or should we introduce a new one?
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.
Ah, darn it. Yes. We need a new flag.
@@ -11,6 +11,10 @@ | |||
|
|||
'use strict'; | |||
|
|||
var DOMProperty = require('DOMProperty'); | |||
|
|||
var HAS_BOOLEAN_VALUE = DOMProperty.injection.MUST_USE_PROPERTY; |
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.
Fixing this up shortly.
// autoSave allows WebKit/Blink to persist values of input fields on page reloads | ||
autoSave: 0, | ||
// Set the string boolean flag to allow the behavior | ||
value: HAS_STRING_BOOLEAN_VALUE, |
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 must be in here for backwards compatibility.
} | ||
return attributeName + '=' + quoteAttributeValueForBrowser(value); |
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'd love to just lean on DOMProperty.shouldSetAttribute
, but reserved props pass through here, and style is in there. Maybe we could remove style
from reserved props.
@@ -597,15 +597,15 @@ describe('ReactDOMServerIntegration', () => { | |||
}); | |||
|
|||
// this probably is just masking programmer error, but it is existing behavior. |
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 comment is now outdated.
expect(e.getAttribute('class')).toBe('true'); | ||
itRenders('no className prop with true value', async render => { | ||
const e = await render(<div className={true} />, 1); | ||
expect(e.hasAttribute('class')).toBe(false); | ||
}); | ||
|
||
// this probably is just masking programmer error, but it is existing behavior. |
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.
Ditto.
@@ -664,15 +664,15 @@ describe('ReactDOMServerIntegration', () => { | |||
}); | |||
|
|||
// this probably is just masking programmer error, but it is existing behavior. | |||
itRenders('htmlFor prop with true value', async render => { |
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.
Here too.
expect(e.getAttribute('for')).toBe('true'); | ||
itRenders('no htmlFor prop with true value', async render => { | ||
const e = await render(<div htmlFor={true} />, 1); | ||
expect(e.hasAttribute('for')).toBe(false); | ||
}); | ||
|
||
// this probably is just masking programmer error, but it is existing behavior. |
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.
And here.
- Adds exceptions to isCustomAttribute for dashed SVG elements - Use consistent custom element check across all modules
My vote is yes. There should be no danger here, and it is the behavior on 15.x |
'font-face-src': true, | ||
'font-face-uri': true, | ||
'missing-glyph': true, | ||
}; |
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.
Didn't see the namespace work. Cool. This is inferior, I'll rework this.
if (tagName.indexOf('-') >= 0 || props.is != null) { | ||
// TODO: We always have a namespace with fiber. Drop the first | ||
// check when Stack is removed. | ||
return namespace == null || namespace === HTML_NAMESPACE; |
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.
@gaearon I moved the namespace check into this module. Stack validates properties before the namespace is assigned to the element, so we don't know if the element is SVG or HTML during initial validations. namespace == null
can go away when stack is removed. This also preserves the custom element behavior for 15.x, if that matters.
@@ -2016,7 +2016,7 @@ describe('ReactDOMComponent', () => { | |||
expect(el.hasAttribute('whatever')).toBe(false); | |||
|
|||
expectDev(console.error.calls.argsFor(0)[0]).toContain( | |||
'Warning: Invalid prop `whatever` on <div> tag', | |||
'Warning: Received `true` for non-boolean attribute `whatever`', |
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.
Why does it warn about being non-boolean for unknown attributes? It seems like we don't know whether an unknown attribute is actually a boolean attribute or not, so this could lead to false positives.
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.
Maybe we can tweak the wording here. But it is intentional that behavior is the same for known and unknown attributes.
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.
In what way is the behavior the same? Known boolean attributes will not warn because of the propertyInfo
checks. I'm not sure I understand why we assume unknown attributes being passed booleans are not boolean attributes.
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.
Known boolean attributes will not warn because of the propertyInfo checks.
Yes, that's what I meant. The only ones for which we pass booleans through are the booleans we know. We don't pass booleans through for either known non-booleans or unknowns.
This keeps it unobservable whether a certain non-boolean attribute is known or unknown. Without this guarantee we can't hide the fact that, for example, src
was cut from the whitelist. Implementation details start to leak out in the behavior.
Of course that means we can never delete booleans from the whitelist. But that seems like a fair tradeoff.
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.
So how can a user correctly use a boolean attribute that we don't have whitelisted?
I don't think behavior changes in previously-known attributes implies implementation details are leaked; it just means attributes behavior has changed. Which is why this is part of a major release. Why not instead utilize warnings in this major release that would allow us to treat any unknown attribute with boolean values as a boolean attribute in the next?
Of course that means we can never delete booleans from the whitelist. But that seems like a fair tradeoff.
I don't agree that having to maintain a boolean whitelist forever is a fair tradeoff, it introduces the same issues we had with the whitelist in the first place, just with a smaller subset of attributes.
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.
<div />; // false
<div bool-attr="" />; // true
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 don't think custom elements should accept booleans coerced to strings neither.
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.
It's easy enough for static attribute values but once you have to start dynamically setting and unsetting boolean attributes you have to start switching between ""
and null
and remember those map to true
and false
, which is unfortunate.
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.
That's when we add them to the whitelist.
There's no consistent pattern for what booleans should do (clear attribute vs "off" vs "false" vs "no"). So we'll always need some whitelist.
In theory we could make the default behavior be whatever has the most attributes following that rule. Do we know for sure which that is?
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.
In my mind the goal is to reduce that inconsistency, which we maintain by coercing booleans to "true"
or "on"
. In the long-term I think it makes sense to require users to be explicit about those enumerated attributes that expect boolean-ish values, and that's something we can add warnings for.
In theory we could make the default behavior be whatever has the most attributes following that rule. Do we know for sure which that is?
Looking at this attribute table in the spec it seems clear that there are far more boolean attributes than there are attributes that accept "true"
/"false"
or "on"
/"off"
55eb136
to
a270e03
Compare
Ah I see now. That's a bit of a bummer because we get the namespace URI from an argument elsewhere. We only read it directly in |
Nope. I'm totally wrong. I'll take a look either way. |
This is a work in progress PR so that @gaearon and I can coordinate.
true
andfalse
.<svg><font-face x-height={false} /></svg>
passes through.NaN
should always be stringified, and should always warn.Bug:<svg><font-face-format string={Symbol()} /></svg>
throwsaria
(exactly) and anything starting withon
(except events) should always be blocked and warn.<div onClick={0}>
should warn. AlsoNaN
,false
.data
before. Should we allow it again?Possible future work:
<font-face>
taking custom element pathvalue
,dangerouslySetInnerHTML
suppressContentEditableWarning
<textarea value>
, etcselectedIndex
? It used to warn, now it confusingly gets set.off
/on
andyes
/no
attributes work as intended.crossorigin
.