Skip to content

Instantly share code, notes, and snippets.

@david-mark
Last active January 18, 2018 16:03
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save david-mark/4ba4094ad7626227530eb98322aa33eb to your computer and use it in GitHub Desktop.
Save david-mark/4ba4094ad7626227530eb98322aa33eb to your computer and use it in GitHub Desktop.
Why Does jQuery Still Contain "Sizzle"?

#Why Does jQuery Still Contain "Sizzle"?

The download of jQuery 3.1 contains the huge and complicated attempt to simulate the standard Selectors API called "Sizzle", allegedly to fix browser bugs.

It also attempts to make all CSS selectors work in supported browsers, but that's fairly pointless. If you need to support queries IE 8, then don't use CSS selectors that are unsupported in IE 8 (you'll know when you hit one as a friendly exception will be thrown). Same goes for IE 9 and 10, which are the only other browsers alleged to be supported by jQuery that contain significant gaps in CSS3 support. And even for those, it's a pretty short list of selectors that are missing in the browsers and supported (or attempted) by Sizzle.

As we'll see, jQuery doesn't support IE 8 at all, other than through old (1.x) and largely unmaintained versions that may well have problems in newer browsers (jQuery's designs have always required perpetual patching). It's also very picky about what versions of standards-based browsers it attempts to support.

So the primary question is: just how useful are these alleged bug fixes?

##jQuery Browser Support

jQuery Brower support is defined as:

Chrome: (Current - 1) and Current

Edge: (Current - 1) and Current

Firefox: (Current - 1) and Current

Internet Explorer: 9+

Safari: (Current - 1) and Current

Opera: Current

Stock browser on Android 4.0+

Safari on iOS 7+

Strange to specify support as "Current" (or "Current - 1") as browsers shift versions constantly these days. Can be sure that bug reports will not be accepted by jQuery if the issue cannot be demonstrated in this very narrow and ever-shifting range. Can imagine that jQuery feature tests and workarounds will be periodically removed to mirror the supported browser versions, which gives new meaning to the dubious claims of "moving the Web forward" (more like leaving users behind).

At the time of this writing, the currently supported browsers include:

Chrome: 54 and 55

Edge: 21?

Firefox: 50 and 49

Internet Explorer: 9+

Safari: 10 and 9 (Mac only is assumed)

Opera: 42

Android stock browser: 4+

Safari on iOS: 7+

##Alleged Browser Bug Fixes

Here are the alleged bug fixes with comments indicating which browsers the bugs were observed in:

// qSa(:focus) reports false when true (Chrome 21)
// We allow this because of a bug in IE8/9 that throws an error
// whenever `document.activeElement` is accessed on an iframe
// So, we allow :focus to pass through QSA all the time to avoid the IE error
// See https://bugs.jquery.com/ticket/13378
rbuggyQSA = [];

if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
    // Build QSA regex
    // Regex strategy adopted from Diego Perini
    assert(function( el ) {
        // Select is set to empty string on purpose
        // This is to test IE's treatment of not explicitly
        // setting a boolean content attribute,
        // since its presence should be enough
        // https://bugs.jquery.com/ticket/12359
        docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
            "<select id='" + expando + "-\r\\' msallowcapture=''>" +
            "<option selected=''></option></select>";

        // Support: IE8, Opera 11-12.16
        // Nothing should be selected when empty strings follow ^= or $= or *=
        // The test attribute must be unknown in Opera but "safe" for WinRT
        // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
        if ( el.querySelectorAll("[msallowcapture^='']").length ) {
            rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
        }

        // Support: IE8
        // Boolean attributes and "value" are not treated correctly
        if ( !el.querySelectorAll("[selected]").length ) {
            rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
        }

        // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
        if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
            rbuggyQSA.push("~=");
        }

        // Webkit/Opera - :checked should return selected option elements
        // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
        // IE8 throws error here and will not see later tests
        if ( !el.querySelectorAll(":checked").length ) {
            rbuggyQSA.push(":checked");
        }

        // Support: Safari 8+, iOS 8+
        // https://bugs.webkit.org/show_bug.cgi?id=136851
        // In-page `selector#id sibling-combinator selector` fails
        if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
            rbuggyQSA.push(".#.+[+~]");
        }
    });

    assert(function( el ) {
        el.innerHTML = "<a href='' disabled='disabled'></a>" +
            "<select disabled='disabled'><option/></select>";

        // Support: Windows 8 Native Apps
        // The type and name attributes are restricted during .innerHTML assignment
        var input = document.createElement("input");
        input.setAttribute( "type", "hidden" );
        el.appendChild( input ).setAttribute( "name", "D" );

        // Support: IE8
        // Enforce case-sensitivity of name attribute
        if ( el.querySelectorAll("[name=d]").length ) {
            rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
        }

        // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
        // IE8 throws error here and will not see later tests
        if ( el.querySelectorAll(":enabled").length !== 2 ) {
            rbuggyQSA.push( ":enabled", ":disabled" );
        }

        // Support: IE9-11+
        // IE's :disabled selector does not pick up the children of disabled fieldsets
        docElem.appendChild( el ).disabled = true;
        if ( el.querySelectorAll(":disabled").length !== 2 ) {
            rbuggyQSA.push( ":enabled", ":disabled" );
        }

        // Opera 10-11 does not throw on post-comma invalid pseudos
        el.querySelectorAll("*,:x");
        rbuggyQSA.push(",.*:");
    });
}

Count nine.

As "Sizzle" is part of jQuery and jQuery can only be expected to work (and is only supported) in a specified range of browsers, let's eliminate all but those that could be useful to jQuery users:

// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
    rbuggyQSA.push("~=");
}

// Support: Safari 8+, iOS 8+
// https://bugs.webkit.org/show_bug.cgi?id=136851
// In-page `selector#id sibling-combinator selector` fails
if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
    rbuggyQSA.push(".#.+[+~]");
}

// Support: IE9-11+
// IE's :disabled selector does not pick up the children of disabled fieldsets
docElem.appendChild( el ).disabled = true;
if ( el.querySelectorAll(":disabled").length !== 2 ) {
    rbuggyQSA.push( ":enabled", ":disabled" );
}

Only three remain that could be considered helpful to jQuery users. Note that the :checked bug is ignored as the accompanying comment indicates only "Webkit/Opera" and Opera has been based on Blink since version 15 (or 27 versions too old to be supported by jQuery).

The first is apparently only helpful in Android Jelly Bean and previous versions, which had its last release on October 3, 2013 and looks to be a very odd case.

The second is apparently a continuing issue with Apple browsers involving sibling combinator queries.

The last one involves disabled fieldsets and is apparently present in IE 9-11 (there are no newer versions of IE).

Also note that if any of the above tests throws an exception, the remaining test are not even considered. This is alluded to by this comment:

// IE8 throws error here and will not see later tests

...and is obviously an incompetent way to go about feature testing.

##Conclusion

Weighing these three alleged fixes against the huge and complicated "Sizzle", it should be clear that its inclusion isn't close to worth it. It should have been deprecated years ago and ultimately removed from jQuery, but as it makes up the majority of jQuery (hence the name) and as jQuery also has its own roster of non-standard selectors (and deviations from the Selectors API where they see fit), jQuery users (and their end-users) will likely be stuck with it indefinitely.

It's a widespread myth that jQuery invented CSS selector queries, and so we have that project to thank for the Selectors API. An oft-heard refrain is that jQuery helped to "move the Web forward" in this way. History says jQuery is actually holding the Web back by making documents larger, more complicated and ever more exclusive, not to mention encouraging the use of non-standard selectors and refusing to conform to the very standard it is often credited for. It's a classic case of Hype Driven Development.

There's only one rational way to deal with CSS selector queries, which is to feature test what we need and simply bail out of related enhancements and applications (or parts of applications) if they fail. In short, try to query the document with representative selectors and, if they manage to return a result without throwing an exception, check to see that the result matches expectations. That's all "Sizzle" does for the small sample of cases that have been discovered by its developers.

Relying on a combination of jQuery and Sizzle, which are maintained and versioned separately, to magically fix all selector issues is just silly. Will leave users with unusable documents in the event of mistakes or non-support and developers wondering why exceptions are emanating from deep inside jQuery in some browsers and not others.

##Side Note on jQuery Feature Detection Strategy

As a side note, only jQuery would use type conversion to try to detect host object methods (and searching the resulting string for "[native" is squarely in the realm of fantasy code). We have only history as our guide to the suitability of feature detection strategies and type conversion is at the top of the ill-advised list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment