Skip to content

Commit 39ccd8b

Browse files
committed
Attributes: Restore boolean attribute & false setter treatment from 3.x
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 Ref jquery/jquery#5452 Ref jquery/api.jquery.com#1243
1 parent 4ee81e4 commit 39ccd8b

File tree

3 files changed

+354
-19
lines changed

3 files changed

+354
-19
lines changed

src/jquery/attributes.js

+109-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,116 @@
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 ] || [] ).indexOf( attrValue ) === -1
34+
) {
35+
migrateWarn( "boolean-attributes",
36+
"Boolean attribute '" + name +
37+
"' value is different from its lowercased name" );
38+
39+
// jQuery <4 attr hooks setup is complex: there are attr
40+
// hooks, bool hooks and selector attr handles. Only
41+
// implement the logic in jQuery >=4 where it's missing
42+
// and there are only attr hooks.
43+
if ( jQueryVersionSince( "4.0.0" ) ) {
44+
return name.toLowerCase();
45+
}
46+
return null;
47+
}
48+
}
49+
50+
return null;
51+
},
52+
53+
set: origAttrHooks.set || function( elem, value, name ) {
54+
if ( jQuery.migrateIsPatchEnabled( "boolean-attributes" ) ) {
55+
if ( value !== name &&
56+
( extraBoolAttrValues[ name ] || [] ).indexOf( value ) === -1
57+
) {
58+
if ( value !== false ) {
59+
migrateWarn( "boolean-attributes",
60+
"Boolean attribute '" + name +
61+
"' is not set to its lowercased name" );
62+
}
63+
64+
if ( value === false ) {
65+
66+
// Remove boolean attributes when set to false
67+
jQuery.removeAttr( elem, name );
68+
} else {
69+
elem.setAttribute( name, name );
70+
}
71+
return name;
72+
}
73+
} else if ( !jQueryVersionSince( "4.0.0" ) ) {
74+
75+
// jQuery <4 uses a private `boolHook` for the boolean attribute
76+
// setter. It's only activated if `attrHook` is not set, but we set
77+
// it here in Migrate. Since we cannot access it, let's just repeat
78+
// its contents here.
79+
if ( value === false ) {
80+
81+
// Remove boolean attributes when set to false
82+
jQuery.removeAttr( elem, name );
83+
} else {
84+
elem.setAttribute( name, name );
85+
}
86+
return name;
87+
}
88+
}
89+
};
90+
} );
91+
92+
migratePatchFunc( jQuery, "attr", function( elem, name, value ) {
93+
var nType = elem.nodeType;
94+
95+
// Fallback to the original method on text, comment and attribute nodes
96+
// and when attributes are not supported.
97+
if ( nType === 3 || nType === 8 || nType === 2 ||
98+
typeof elem.getAttribute === "undefined" ) {
99+
return oldJQueryAttr.apply( this, arguments );
100+
}
101+
102+
if ( value === false && name.toLowerCase().indexOf( "aria-" ) !== 0 &&
103+
!rbooleans.test( name ) ) {
104+
migrateWarn( "attr-false",
105+
"Setting the non-ARIA non-boolean attribute '" + name +
106+
"' to false" );
107+
108+
jQuery.attr( elem, name, "false" );
109+
return;
110+
}
111+
112+
return oldJQueryAttr.apply( this, arguments );
113+
}, "attr-false" );
7114

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

0 commit comments

Comments
 (0)