From 1083f82d1ee0c8f15a66be15e6184294a69d4420 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 18 Feb 2013 23:52:29 -0500 Subject: [PATCH] Fix #13434: native-API selector module What's out: * 6 KB * attribute not equal selector * positional selectors (:first; :eq(n); :odd; etc.) * type selectors (:input; :checkbox; :button; etc.) * state-based selectors (:animated; :visible; :hidden; etc.) * :has(selector) * custom selectors * leading combinators (e.g., $collection.find("> *")) * reliable functionality on XML fragments * requiring all parts of a selector to match elements under context (e.g., $div.find("div > *") now matches children of $div) * matching against non-elements * reliable sorting of disconnected nodes --- Gruntfile.js | 44 +++++++++------ src/selector-native.js | 125 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 src/selector-native.js diff --git a/Gruntfile.js b/Gruntfile.js index f202ac8fe..7ff43035e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,7 +22,7 @@ module.exports = function( grunt ) { files: distpaths }, selector: { - destFile: "src/selector.js", + destFile: "src/selector-sizzle.js", apiFile: "src/sizzle-jquery.js", srcFile: "src/sizzle/sizzle.js" }, @@ -39,10 +39,9 @@ module.exports = function( grunt ) { "src/queue.js", "src/attributes.js", "src/event.js", - "src/selector.js", + { flag: "sizzle", src: "src/selector-sizzle.js", alt: "src/selector-native.js" }, "src/traversing.js", "src/manipulation.js", - { flag: "css", src: "src/css.js" }, "src/serialize.js", { flag: "event-alias", src: "src/event-alias.js" }, @@ -145,8 +144,7 @@ module.exports = function( grunt ) { }); }); - // Build src/selector.js - grunt.registerTask( "selector", "Build src/selector.js", function() { + grunt.registerTask( "selector", "Build Sizzle-based selector module", function() { var cfg = grunt.config("selector"), name = cfg.destFile, @@ -190,7 +188,7 @@ module.exports = function( grunt ) { // Rejoin the pieces compiled = parts.join(""); - grunt.verbose.write("Injected sizzle-jquery.js into sizzle.js"); + grunt.verbose.writeln("Injected " + cfg.apiFile + " into " + cfg.srcFile); // Write concatenated source to file, and ensure newline-only termination grunt.file.write( name, compiled.replace( /\x0d\x0a/g, "\x0a" ) ); @@ -328,37 +326,47 @@ module.exports = function( grunt ) { var flag = filepath.flag, specified = false, omit = false, - message = ""; + messages = []; if ( flag ) { if ( excluded[ flag ] !== undefined ) { - message = ( "Excluding " + flag ).red; + messages.push([ + ( "Excluding " + flag ).red, + ( "(" + filepath.src + ")" ).grey + ]); specified = true; - omit = true; - } else { - message = ( "Including " + flag ).green; + omit = !filepath.alt; + if ( !omit ) { + flag += " alternate"; + filepath.src = filepath.alt; + } + } + if ( excluded[ flag ] === undefined ) { + messages.push([ + ( "Including " + flag ).green, + ( "(" + filepath.src + ")" ).grey + ]); // If this module was actually specified by the - // builder, then st the flag to include it in the + // builder, then set the flag to include it in the // output list if ( modules[ "+" + flag ] ) { specified = true; } } + filepath = filepath.src; + // Only display the inclusion/exclusion list when handling // an explicit list. // // Additionally, only display modules that have been specified // by the user if ( explicit && specified ) { - grunt.log.writetableln([ 27, 30 ], [ - message, - ( "(" + filepath.src + ")").grey - ]); + messages.forEach(function( message ) { + grunt.log.writetableln( [ 27, 30 ], message ); + }); } - - filepath = filepath.src; } if ( !omit ) { diff --git a/src/selector-native.js b/src/selector-native.js new file mode 100644 index 000000000..11bf32507 --- /dev/null +++ b/src/selector-native.js @@ -0,0 +1,125 @@ +var selector_hasDuplicate, + matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector, + selector_sortOrder = function( a, b ) { + // Flag for duplicate removal + if ( a === b ) { + selector_hasDuplicate = true; + return 0; + } + + var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); + + if ( compare ) { + // Disconnected nodes + if ( compare & 1 ) { + + // Choose the first element that is related to our document + if ( a === document || jQuery.contains(document, a) ) { + return -1; + } + if ( b === document || jQuery.contains(document, b) ) { + return 1; + } + + // Maintain original order + return 0; + } + + return compare & 4 ? -1 : 1; + } + + // Not directly comparable, sort on existence of method + return a.compareDocumentPosition ? -1 : 1; + }; + +jQuery.extend({ + find: function( selector, context, results, seed ) { + var elem, + i = 0; + + results = results || []; + context = context || document; + + if ( seed ) { + while ( (elem = seed[i++]) ) { + if ( jQuery.find.matchesSelector(elem, selector) ) { + results.push( elem ); + } + } + } else { + jQuery.merge( results, context.querySelectorAll(selector) ); + } + + return results; + }, + unique: function( results ) { + var elem, + duplicates = [], + i = 0, + j = 0; + + selector_hasDuplicate = false; + results.sort( selector_sortOrder ); + + if ( selector_hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; + }, + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += jQuery.text( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + return elem.textContent; + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }, + contains: function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && adown.contains(bup) ); + }, + isXMLDoc: function( elem ) { + return (elem.ownerDocument || elem).documentElement.nodeName !== "HTML"; + }, + expr: { + match: { + needsContext: /^[\x20\t\r\n\f]*[>+~]/ + } + } +}); + +jQuery.extend( jQuery.find, { + matches: function( expr, elements ) { + return jQuery.find( expr, null, null, elements ); + }, + matchesSelector: function( elem, expr ) { + return matches.call( elem, expr ); + } +});