Skip to content

Commit 5471109

Browse files
authored
Attributes: Restore boolean attribute & false setter treatment from 3.x (#540)
Restore & warn against: * boolean attributes set to something different than their lowercase names * boolean attributes queried when set to something different than their lowercase names * non-boolean non-ARIA attributes set to `false` Fixes gh-504 Closes gh-540 Ref jquery/jquery#5452 Ref jquery/api.jquery.com#1243
1 parent 6c02753 commit 5471109

File tree

3 files changed

+373
-19
lines changed

3 files changed

+373
-19
lines changed

src/jquery/attributes.js

+111-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,118 @@
11
import { migratePatchFunc, migrateWarn } from "../main.js";
2+
import { jQueryVersionSince } from "../compareVersions.js";
23

34
var oldRemoveAttr = jQuery.fn.removeAttr,
5+
oldJQueryAttr = jQuery.attr,
46
oldToggleClass = jQuery.fn.toggleClass,
5-
rbooleans = /^(?:checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$/i,
6-
rmatchNonSpace = /\S+/g;
7+
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|" +
8+
"disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
9+
rbooleans = new RegExp( "^(?:" + booleans + ")$", "i" ),
10+
rmatchNonSpace = /\S+/g,
11+
12+
// Some formerly boolean attributes gained new values with special meaning.
13+
// Skip the old boolean attr logic for those values.
14+
extraBoolAttrValues = {
15+
hidden: [ "until-found" ]
16+
};
17+
18+
// HTML boolean attributes have special behavior:
19+
// we consider the lowercase name to be the only valid value, so
20+
// getting (if the attribute is present) normalizes to that, as does
21+
// setting to any non-`false` value (and setting to `false` removes the attribute).
22+
// See https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
23+
jQuery.each( booleans.split( "|" ), function( _i, name ) {
24+
var origAttrHooks = jQuery.attrHooks[ name ] || {};
25+
jQuery.attrHooks[ name ] = {
26+
get: origAttrHooks.get || function( elem ) {
27+
var attrValue;
28+
29+
if ( jQuery.migrateIsPatchEnabled( "boolean-attributes" ) ) {
30+
attrValue = elem.getAttribute( name );
31+
32+
if ( attrValue !== name && attrValue != null &&
33+
( extraBoolAttrValues[ name ] || [] )
34+
.indexOf( String( attrValue ).toLowerCase() ) === -1
35+
) {
36+
migrateWarn( "boolean-attributes",
37+
"Boolean attribute '" + name +
38+
"' value is different from its lowercased name" );
39+
40+
// jQuery <4 attr hooks setup is complex: there are attr
41+
// hooks, bool hooks and selector attr handles. Only
42+
// implement the logic in jQuery >=4 where it's missing
43+
// and there are only attr hooks.
44+
if ( jQueryVersionSince( "4.0.0" ) ) {
45+
return name.toLowerCase();
46+
}
47+
return null;
48+
}
49+
}
50+
51+
return null;
52+
},
53+
54+
set: origAttrHooks.set || function( elem, value, name ) {
55+
if ( jQuery.migrateIsPatchEnabled( "boolean-attributes" ) ) {
56+
if ( value !== name &&
57+
( extraBoolAttrValues[ name ] || [] )
58+
.indexOf( String( value ).toLowerCase() ) === -1
59+
) {
60+
if ( value !== false ) {
61+
migrateWarn( "boolean-attributes",
62+
"Boolean attribute '" + name +
63+
"' is not set to its lowercased name" );
64+
}
65+
66+
if ( value === false ) {
67+
68+
// Remove boolean attributes when set to false
69+
jQuery.removeAttr( elem, name );
70+
} else {
71+
elem.setAttribute( name, name );
72+
}
73+
return name;
74+
}
75+
} else if ( !jQueryVersionSince( "4.0.0" ) ) {
76+
77+
// jQuery <4 uses a private `boolHook` for the boolean attribute
78+
// setter. It's only activated if `attrHook` is not set, but we set
79+
// it here in Migrate. Since we cannot access it, let's just repeat
80+
// its contents here.
81+
if ( value === false ) {
82+
83+
// Remove boolean attributes when set to false
84+
jQuery.removeAttr( elem, name );
85+
} else {
86+
elem.setAttribute( name, name );
87+
}
88+
return name;
89+
}
90+
}
91+
};
92+
} );
93+
94+
migratePatchFunc( jQuery, "attr", function( elem, name, value ) {
95+
var nType = elem.nodeType;
96+
97+
// Fallback to the original method on text, comment and attribute nodes
98+
// and when attributes are not supported.
99+
if ( nType === 3 || nType === 8 || nType === 2 ||
100+
typeof elem.getAttribute === "undefined" ) {
101+
return oldJQueryAttr.apply( this, arguments );
102+
}
103+
104+
if ( value === false && name.toLowerCase().indexOf( "aria-" ) !== 0 &&
105+
!rbooleans.test( name ) ) {
106+
migrateWarn( "attr-false",
107+
"Setting the non-ARIA non-boolean attribute '" + name +
108+
"' to false" );
109+
110+
jQuery.attr( elem, name, "false" );
111+
return;
112+
}
113+
114+
return oldJQueryAttr.apply( this, arguments );
115+
}, "attr-false" );
7116

8117
migratePatchFunc( jQuery.fn, "removeAttr", function( name ) {
9118
var self = this,

0 commit comments

Comments
 (0)