Skip to content

Commit

Permalink
Attributes: Restore boolean attribute & false setter treatment from 3.x
Browse files Browse the repository at this point in the history
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 jquerygh-504
Ref jquery/jquery#5452
Ref jquery/api.jquery.com#1243
  • Loading branch information
mgol committed Oct 8, 2024
1 parent 5487a65 commit e511899
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 19 deletions.
111 changes: 109 additions & 2 deletions src/jquery/attributes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,116 @@
import { migratePatchFunc, migrateWarn } from "../main.js";
import { jQueryVersionSince } from "../compareVersions.js";

var oldRemoveAttr = jQuery.fn.removeAttr,
oldJQueryAttr = jQuery.attr,
oldToggleClass = jQuery.fn.toggleClass,
rbooleans = /^(?:checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$/i,
rmatchNonSpace = /\S+/g;
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|" +
"disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
rbooleans = new RegExp( "^(?:" + booleans + ")$", "i" ),
rmatchNonSpace = /\S+/g,

// Some formerly boolean attributes gained new values with special meaning.
// Skip the old boolean attr logic for those values.
extraBoolAttrValues = {
hidden: [ "until-found" ]
};

// HTML boolean attributes have special behavior:
// we consider the lowercase name to be the only valid value, so
// getting (if the attribute is present) normalizes to that, as does
// setting to any non-`false` value (and setting to `false` removes the attribute).
// See https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
jQuery.each( booleans.split( "|" ), function( _i, name ) {
var origAttrHooks = jQuery.attrHooks[ name ] || {};
jQuery.attrHooks[ name ] = {
get: origAttrHooks.get || function( elem ) {
var attrValue;

if ( jQuery.migrateIsPatchEnabled( "boolean-attributes" ) ) {
attrValue = elem.getAttribute( name );

if ( attrValue !== name && attrValue != null &&
( extraBoolAttrValues[ name ] || [] ).indexOf( attrValue ) === -1
) {
migrateWarn( "boolean-attributes",
"Boolean attribute '" + name +
"' value is different from its lowercased name" );

// jQuery <4 attr hooks setup is complex: there are attr
// hooks, bool hooks and selector attr handles. Only
// implement the logic in jQuery >=4 where it's missing
// and there are only attr hooks.
if ( jQueryVersionSince( "4.0.0" ) ) {
return name.toLowerCase();
}
return null;
}
}

return null;
},

set: origAttrHooks.set || function( elem, value, name ) {
if ( jQuery.migrateIsPatchEnabled( "boolean-attributes" ) ) {
if ( value !== name &&
( extraBoolAttrValues[ name ] || [] ).indexOf( value ) === -1
) {
if ( value !== false ) {
migrateWarn( "boolean-attributes",
"Boolean attribute '" + name +
"' is not set to its lowercased name" );
}

if ( value === false ) {

// Remove boolean attributes when set to false
jQuery.removeAttr( elem, name );
} else {
elem.setAttribute( name, name );
}
return name;
}
} else if ( !jQueryVersionSince( "4.0.0" ) ) {

// jQuery <4 uses a private `boolHook` for the boolean attribute
// setter. It's only activated if `attrHook` is not set, but we set
// it here in Migrate. Since we cannot access it, let's just repeat
// its contents here.
if ( value === false ) {

// Remove boolean attributes when set to false
jQuery.removeAttr( elem, name );
} else {
elem.setAttribute( name, name );
}
return name;
}
}
};
} );

migratePatchFunc( jQuery, "attr", function( elem, name, value ) {
var nType = elem.nodeType;

// Fallback to the original method on text, comment and attribute nodes
// and when attributes are not supported.
if ( nType === 3 || nType === 8 || nType === 2 ||
typeof elem.getAttribute === "undefined" ) {
return oldJQueryAttr.apply( this, arguments );
}

if ( value === false && name.toLowerCase().indexOf( "aria-" ) !== 0 &&
!rbooleans.test( name ) ) {
migrateWarn( "attr-false",
"Setting the non-ARIA non-boolean attribute '" + name +
"' to false" );

jQuery.attr( elem, name, "false" );
return;
}

return oldJQueryAttr.apply( this, arguments );
}, "attr-false" );

migratePatchFunc( jQuery.fn, "removeAttr", function( name ) {
var self = this,
Expand Down
Loading

0 comments on commit e511899

Please sign in to comment.