10000 Update the rules for boolean attributes for 4.0 · Issue #5388 · jquery/jquery · GitHub
[go: up one dir, main page]

Skip to content

Update the rules for boolean attributes for 4.0 #5388

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

Closed
mgol opened this issue Jan 10, 2024 · 8 comments · Fixed by #5452
Closed

Update the rules for boolean attributes for 4.0 #5388

mgol opened this issue Jan 10, 2024 · 8 comments · Fixed by #5452

Comments

@mgol
Copy link
Member
mgol commented Jan 10, 2024

Our list of boolean attributes is outdated; it is missing several boolean attributes from HTML Attributes, includes a scoped that isn't even present there, and treats hidden as boolean despite the expansion of that attribute to be an enumeration including "until-found" by whatwg/html@f5def65.

Originally posted by @gibson042 in #5384 (comment)

@mgol
Copy link
Member Author
mgol commented Jan 10, 2024

Oh, interesting. Perhaps we should update this list for 4.0 as this can be considered a breaking change.

Or we should get rid of this manual normalization, depending on users doing the right thing here. The biggest issue are possibly falsy checks on values since an empty string is also a valid value. This was also raised by @timmywil in a past issue where I proposed a similar simlification: #2946.

If we're worried about compat for such things, my guess would be we could limit this list to just a few entries like checked & selected. I feel keeping the list open-ended leads us to the same problems as the logic for auto-appending px - the list is never complete and modifying this is both a maintenance burden as well as a potential source of application breakage. (the px behavior was change to have a closed list in #4055)

Our list right now is:

  • checked
  • selected
  • async
  • autofocus
  • autoplay
  • controls
  • defer
  • disabled
  • hidden
  • ismap
  • loop
  • multiple
  • open
  • readonly
  • required
  • scoped

As Richard mentioned, hidden is no longer even correct here but removing it from the list is a compat break so it'd be good to fit it into 4.0.

If we want to keep the logic for at least some attributes, I could see us doing so just for checked & selected; maybe disabled. That said, I'd expect hidden to also be a commonly used one and a good case where our behavior causes issues for future standards changes. If another name from the list gets similar treatment in standards, we won't be able to adjust without another breaking change. It might be more future-proof to give up on this normalization in 4.0 in the end.

Thoughts, @timmywil @gibson042?

@mgol mgol added Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. Attributes Blocker labels Jan 10, 2024
@mgol mgol added this to the 4.0.0 milestone Jan 10, 2024
@mgol
Copy link
Member Author
mgol commented Jan 10, 2024

Tentatively adding it to 4.0.0 blockers so that we don't release before discussing this first.

@timmywil timmywil removed the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Feb 5, 2024
@mgol mgol self-assigned this Mar 12, 2024
@mgol
Copy link
Member Author
mgol commented Mar 12, 2024

I did a query on GitHub for calls like ".attr('disabled', false)" - and the same for other attributes - to see their popularity. This is not ideal - it only counts one type of quotes, it may not catch some formatting styles but it seems to correlate well with overall popularity. Here are the results with links:

Searches for all the other attributes generated less than 50 results.

I was surprised to see so few uses of the selected setter. Based on the above and on the fact that generalizing the false setter to mean attribute removal could break currently working code which depends on false being stringified to "false", I propose to:

  1. Remove the boolean attribute getter completely - always return the attribute value instead of its name
  2. Remove the boolean attribute setter for all attributes with two exceptions: disabled & checked
  3. For disabled & checked, limit the setter hook to remove for the false value but remove the value normalization - set the value the user provided instead of the attribute name.

Of course, that is a breaking change; the few most common breakages:

  1. The getter may return a different value than in 3.x. If the code compares it directly to the attribute name, it will break.
  2. Even if strict comparison is not used, code may check for the value truthiness. If the setter was used with an empty string, the getter would return a falsy value.

We can add Migrate warnings for both cases.

Thoughts?

@mgol mgol added the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Mar 12, 2024
@gibson042
Copy link
Member

Given that any change is breaking for some cases (albeit possibly only hypothetical) and that the current behavior is wrong and will become more wrong over time as HTML expand more currently-boolean attributes to include other values, I am of the opinion that we should remove special treatment for any boolean attribute, generalizing the .attr(name, false) setter to remove any attribute (which is both convenient behavior and important for existing code) and otherwise reflecting the DOM as-is. It would be unnecessarily confusing and inconsistent to privilege only disabled and checked.

@mgol
Copy link
Member Author
mgol commented Mar 18, 2024

@gibson042

I am of the opinion that we should remove special treatment for any boolean attribute, generalizing the .attr(name, false) setter to remove any attribute (which is both convenient behavior and important for existing code) and otherwise reflecting the DOM as-is.

I continue to be worried about the same as what I mentioned at one of our meetings - there are attributes with a valid "false" value and lots of code depends on false getting stringified to that value instead of causing the attribute to be deleted. This seems to especially apply to ARIA attributes, see e.g. the search for ".attr('aria-hidden', false)" or ".attr('aria-checked', false)". It's also not just ARIA - the HTML spec mentions a few others in its non-normative Attributes table: contenteditable, enctype, spellcheck, writingsuggestions. I suspect code setting data-* attributes may also depend on false stringification. What if more attributes start treating "false" as a valid value?

There's prior art for us special casing some inputs due to backwards compat even if we preferred to drop the code altogether; the logic when to auto-append px to numeric values in .css() is a good example:

var ralphaStart = /^[a-z]/,
// The regex visualized:
//
// /----------\
// | | /-------\
// | / Top \ | | |
// /--- Border ---+-| Right |-+---+- Width -+---\
// | | Bottom | |
// | \ Left / |
// | |
// | /----------\ |
// | /-------------\ | | |- END
// | | | | / Top \ | |
// | | / Margin \ | | | Right | | |
// |---------+-| |-+---+-| Bottom |-+----|
// | \ Padding / \ Left / |
// BEGIN -| |
// | /---------\ |
// | | | |
// | | / Min \ | / Width \ |
// \--------------+-| |-+---| |---/
// \ Max / \ Height /
rautoPx = /^(?:Border(?:Top|Right|Bottom|Left)?(?:Width|)|(?:Margin|Padding)?(?:Top|Right|Bottom|Left)?|(?:Min|Max)?(?:Width|Height))$/;
export function isAutoPx( prop ) {
// The first test is used to ensure that:
// 1. The prop starts with a lowercase letter (as we uppercase it for the second regex).
// 2. The prop is not empty.
return ralphaStart.test( prop ) &&
rautoPx.test( prop[ 0 ].toUpperCase() + prop.slice( 1 ) );
}

8000

@mgol
Copy link
Member Author
mgol commented Mar 18, 2024

I thought about my original proposal and I think if we want to special-case, we should include more attributes, though, perhaps all with >50 search results above.

Our unit tests also tell a story - if we special cased not just false to mean attribute removal but also true for the value to replicate the attribute name, almost all our existing tests are passing.

Depending on how much we want to play safe, we could even keep a version of the getter that converts an empty string to the attribute name and returns the other values unchanged. That would make the HTML like <input checked> and the check if ( $( input ).attr( "checked" ) ) { /* ... */ } still work, avoiding the issue with an empty string being falsy. This pattern is, unfortunately, pretty popular; see the search for ".attr('checked')) {".

It would be hard to warn against this in Migrate as we can only patch the getter there, we cannot see it's being used as a truthy check.

@mgol
Copy link
Member Author
mgol commented Mar 18, 2024

I submitted #5451 implementing the conservative approach I mentioned above. We can make it less conservative be removing one or two parts from it. Let's discuss this today.

We can also go with Richard's solution of generalizing false - but we need to assess the breaking change from removing stringification to "false" for attributes where this is a valid value.

@timmywil timmywil removed the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Mar 18, 2024
@mgol mgol changed the title Update the list of boolean attributes for 4.0 Update the rules for boolean attributes for 4.0 Mar 18, 2024
mgol added a commit to mgol/jquery that referenced this issue Mar 18, 2024
The HTML spec defines boolean attributes:
https://html.spec.whatwg.org/#boolean-attributes
that often correlate with boolean properties. If the attribute is missing, it
correlates with the `false` property value, if it's present - the `true`
property value. The only valid values are an empty string or the attribute name.

jQuery tried to be helpful here and treated boolean attributes in a special way
in the `.attr()` API:
1. For the getter, as long as the attribute was present, it was returning the
   attribute name lowercased, ignoring the value.
2. For the setter, it was removing the attribute when `false` was passed;
   otherwise, it was ignoring the passed value and set the attribute -
   interestingly, in jQuery `>=3` not lowercased anymore.

The problem is the spec occasionally converts boolean attributes into ones with
additional attribute values with special behavior - one such example is the new
`"until-found"` value for the `hidden` attribute. Our setter normalization
means passing those values is impossible with jQuery. Also, new boolean
attributes are introduced occasionally and jQuery cannot easily add them to the
list without incurring breaking changes.

This patch removes any special handling of boolean attributes - the getter
returns the value as-is and the setter sets the provided value.

To provide better backwards compatibility with the very frequent `false` value
p
8000
rovided to remove the attribute, this patch makes `false` trigger attribute
removal for ALL non-ARIA attributes. ARIA attributes are exempt from the rule
since many of them recognize `"false"` as a valid value with semantics different
than the attribute missing. To remove an ARIA attribute, use `.removeAttr()` or
pass `null` as the value to `.attr()` which doesn't have this exception.

Fixes jquerygh-5388

Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
@mgol
Copy link
Member Author
mgol commented Mar 18, 2024

At the meeting, we decided to:

  1. Remove any special boolean attributes treatment
  2. Make the false value trigger attribute removal for all non-ARIA attributes, stringify it to "false" for ARIA attributes

PR #5452 implements this proposal.

mgol added a commit that referenced this issue Mar 19, 2024
The HTML spec defines boolean attributes:
https://html.spec.whatwg.org/#boolean-attributes
that often correlate with boolean properties. If the attribute is missing, it
correlates with the `false` property value, if it's present - the `true`
property value. The only valid values are an empty string or the attribute name.

jQuery tried to be helpful here and treated boolean attributes in a special way
in the `.attr()` API:
1. For the getter, as long as the attribute was present, it was returning the
   attribute name lowercased, ignoring the value.
2. For the setter, it was removing the attribute when `false` was passed;
   otherwise, it was ignoring the passed value and set the attribute -
   interestingly, in jQuery `>=3` not lowercased anymore.

The problem is the spec occasionally converts boolean attributes into ones with
additional attribute values with special behavior - one such example is the new
`"until-found"` value for the `hidden` attribute. Our setter normalization
means passing those values is impossible with jQuery. Also, new boolean
attributes are introduced occasionally and jQuery cannot easily add them to the
list without incurring breaking changes.

This patch removes any special handling of boolean attributes - the getter
returns the value as-is and the setter sets the provided value.

To provide better backwards compatibility with the very frequent `false` value
provided to remove the attribute, this patch makes `false` trigger attribute
removal for ALL non-ARIA attributes. ARIA attributes are exempt from the rule
since many of them recognize `"false"` as a valid value with semantics different
than the attribute missing. To remove an ARIA attribute, use `.removeAttr()` or
pass `null` as the value to `.attr()` which doesn't have this exception.

Fixes gh-5388
Closes gh-5452

Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
mgol added a commit to mgol/jquery that referenced this issue Jan 5, 2025
The `hidden` attribute used to be a boolean one but it gained a new
`until-found` eventually. This led us to change the way we handle boolean
attributes in jQuery 4.0 in jquerygh-5452 to avoid these issues in the future.

That said, currently from the attributes we treat as boolean only `hidden`
has gained an extra value, let's support it.

Ref jquerygh-5388
Ref jquerygh-5452
mgol added a commit to mgol/jquery that referenced this issue Jan 5, 2025
The `hidden` attribute used to be a boolean one but it gained a new
`until-found` eventually. This led us to change the way we handle boolean
attributes in jQuery 4.0 in jquerygh-5452 to avoid these issues in the future.

That said, currently from the attributes we treat as boolean only `hidden`
has gained an extra value, let's support it.

Ref jquerygh-5388
Ref jquerygh-5452
mgol added a commit to mgol/jquery that referenced this issue Jan 5, 2025
The `hidden` attribute used to be a boolean one but it gained a new
`until-found` eventually. This led us to change the way we handle boolean
attributes in jQuery 4.0 in jquerygh-5452 to avoid these issues in the future.

That said, currently from the attributes we treat as boolean only `hidden`
has gained an extra value, let's support it.

Ref jquerygh-5388
Ref jquerygh-5452
mgol added a commit to mgol/jquery that referenced this issue Jan 14, 2025
The `hidden` attribute used to be a boolean one but it gained a new
`until-found` eventually. This led us to change the way we handle boolean
attributes in jQuery 4.0 in jquerygh-5452 to avoid these issues in the future.

That said, currently from the attributes we treat as boolean only `hidden`
has gained an extra value, let's support it.

Ref jquerygh-5388
Ref jquerygh-5452
mgol added a commit to mgol/jquery that referenced this issue Jan 16, 2025
The `hidden` attribute used to be a boolean one but it gained a new
`until-found` eventually. This led us to change the way we handle boolean
attributes in jQuery 4.0 in jquerygh-5452 to avoid these issues in the future.

That said, currently from the attributes we treat as boolean only `hidden`
has gained an extra value, let's support it.

Ref jquerygh-5388
Ref jquerygh-5452
mgol added a commit that referenced this issue Jan 16, 2025
The `hidden` attribute used to be a boolean one but it gained a new
`until-found` eventually. This led us to change the way we handle boolean
attributes in jQuery 4.0 in gh-5452 to avoid these issues in the future.

That said, currently from the attributes we treat as boolean only `hidden`
has gained an extra value, let's support it.

Closes gh-5607
Ref gh-5388
Ref gh-5452
@jquery jquery deleted a comment Feb 4, 2025
@jquery jquery locked as resolved and limited conversation to collaborators Feb 4, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
0