Skip to content
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

Attributes: Warn & restore boolean attribute & false setter treatment from 3.x #540

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 111 additions & 2 deletions src/jquery/attributes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,118 @@
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( String( attrValue ).toLowerCase() ) === -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;
Comment on lines +44 to +47
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works in jQuery 3.x because - while boolHooks being a fallback for boolean attributes if attrHooks[ name ] didn't exist would not get picked up here - boolHooks only handle a setter and we're in a getter here. The getter - if the get attr hook is missing or returns null - is handled via a call to jQuery.find.attr( elem, name ) which is Sizzle.attr( elem, name ), that defers to Sizzle.selectors.attrHandle, exposed to jQuery via jQuery.expr.attrHandle and that, in turn, is set for boolean attrs by jQuery.

}
}

return null;
},

set: origAttrHooks.set || function( elem, value, name ) {
if ( jQuery.migrateIsPatchEnabled( "boolean-attributes" ) ) {
if ( value !== name &&
( extraBoolAttrValues[ name ] || [] )
.indexOf( String( value ).toLowerCase() ) === -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
Loading