From: Manolo Carrasco Date: Mon, 19 Jul 2010 09:57:36 +0000 (+0000) Subject: Replaced Sizzle port by a pure jsni sizzle implementation which performs better until... X-Git-Tag: release-1.3.2~662 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4134949fd5cb704e599a9f301efb635cd4fabcc1;p=gwtquery.git Replaced Sizzle port by a pure jsni sizzle implementation which performs better until SizzleGwt was finished. Added cache to CssToXPath selector. --- diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java index 16fd62f8..c3a9c033 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java @@ -941,8 +941,8 @@ public class GQuery implements Lazy { /** * Remove all child nodes from the set of matched elements. - * In the case of a document element, it removes all the content. - * You should call empty() when you create a new iframe and you + * In the case of a document element, it removes all the content + * You should call this method whenever you create a new iframe and you * want to add dynamic content to it. */ public GQuery empty() { diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java index 4aced3b6..e5b6ecd6 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java @@ -16,6 +16,7 @@ package com.google.gwt.query.client.impl; import java.util.ArrayList; +import java.util.HashMap; import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.Element; @@ -29,10 +30,12 @@ import com.google.gwt.query.client.SelectorEngine; /** * Runtime selector engine implementation which translates selectors to XPath * and delegates to document.evaluate(). - * It is based on the regular expressions in Andrea Giammarchi's Css2Xpath + * It is based on the regular expressions taken from Andrea Giammarchi's Css2Xpath */ public class SelectorEngineCssToXPath extends SelectorEngineImpl { + HashMap cache = new HashMap(); + /** * Interface for callbacks in replaceAll operations. */ @@ -185,10 +188,12 @@ public class SelectorEngineCssToXPath extends SelectorEngineImpl { public NodeList select(String sel, Node ctx) { JSArray elm = JSArray.create(); - if (!sel.startsWith("./") && !sel.startsWith("/")) { - sel = css2Xpath(sel); + String xsel = cache.get(sel); + if (xsel == null) { + xsel = sel.startsWith("./") || sel.startsWith("/") ? sel : css2Xpath(sel); + cache.put(sel, xsel); } - SelectorEngine.xpathEvaluate(sel, ctx, elm); + SelectorEngine.xpathEvaluate(xsel, ctx, elm); return GQUtils.unique(elm.> cast()).cast(); } diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java index 5d150e1d..d983481a 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java @@ -21,714 +21,1097 @@ import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; -import com.google.gwt.query.client.GQUtils; /** - * Pure Javascript Selector Engine Implementation based on - * Sizzle CSS Selector Engine v1.0. + * Original Sizzle CSS Selector Engine v1.0 inserted in a JSNI method. + * The only difference with the original is that it uses the window.GQS + * instead of window.Sizzle to avoid interfering with the original one. */ public class SelectorEngineSizzle extends SelectorEngineImpl { - public static native boolean contains(Object a, Object b) /*-{ - var ret = - document.compareDocumentPosition ? - (a.compareDocumentPosition(b) & 16): - a !== b && (a.contains ? a.contains(b) : true); - return ret ? true : false; - }-*/; + + public static native void initialize() /*-{ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var GQS = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = GQS.isXML(context), + soFar = selector, ret, cur, pop, i; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + GQS( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); - public static native JavaScriptObject createExpr() /*-{ - var done = 0; - $wnd.Expr = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/, - CHUNKER: /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g - }, - leftMatch: {}, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); } - }, - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; - if ( isTag ) { - part = part.toLowerCase(); - } - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = GQS.find( parts.shift(), context, contextXML ); + context = ret.expr ? GQS.filter( ret.expr, ret.set )[0] : ret.set[0]; + } - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - if ( isPartStrNotTag ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( part, checkSet, true ); - } - }, - ">": function(checkSet, part){ - var isPartStr = typeof part === "string"; - if ( isPartStr && !/\W/.test(part) ) { - part = part.toLowerCase(); - - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - if ( isPartStr ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( part, checkSet, true ); - } - } - }, - "": function(checkSet, part){ - var doneName = done++; - if ( typeof part === "string" && !/\W/.test(part) ) { - checkFn = $wnd.dirNodeCheck; - @com.google.gwt.query.client.impl.SelectorEngineSizzle::dirNodeCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("parentNode", part, doneName, checkSet); - } else { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::dirCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("parentNode", part, doneName, checkSet); - } - }, - "~": function(checkSet, part){ - var doneName = done++; - if ( typeof part === "string" && !/\W/.test(part) ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::dirNodeCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("previousSibling", part, doneName, checkSet); - } else { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::dirCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("previousSibling", part, doneName, checkSet); - } + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + GQS.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? GQS.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); } - }, - find: { - ID: function(match, context){ - if ( typeof context.getElementById !== "undefined") { - var m = context.getElementById(match[1]); - return m ? [m] : []; - } - }, - NAME: function(match, context){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - return ret.length === 0 ? null : ret; + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + GQS.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && GQS.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + GQS( extra, origContext, results, seed ); + GQS.uniqueSort( results ); + } + + return results; +}; + +GQS.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +GQS.matches = function(expr, set){ + return GQS(expr, null, null, set); +}; + +GQS.find = function(expr, context, isXML){ + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +GQS.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && GQS.isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not){ - match = " " + match[1].replace(/\\/g, "") + " "; - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; } - } else if ( inplace ) { - curLoop[i] = false; + } else if ( pass ) { + result.push( item ); + anyFound = true; } } } - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - return match[1].toLowerCase(); - }, - CHILD: function(match){ - if ( match[1] === "nth" ) { - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - match[0] = done++; - return match; - }, - ATTR: function(match, curLoop, inplace, result, not){ - var name = match[1].replace(/\\/g, ""); - if ($wnd.Expr.attrMap[name] ) { - match[1] = $wnd.Expr.attrMap[name]; - } - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; } - return match; - }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( $wnd.Expr.match.CHUNKER.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = @com.google.gwt.query.client.impl.SelectorEngineSizzle::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(match[3], null, null, curLoop); - } else { - var ret = @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); - } - return false; - } - } else if ( $wnd.Expr.match.POS.test( match[0] ) || $wnd.Expr.match.CHILD.test( match[0] ) ) { - return true; + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; } - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - // Accessing this property makes selected-by-default - // options in Safari work properly - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!@com.google.gwt.query.client.impl.SelectorEngineSizzle::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(match[3], elem, null, null).length; - }, - header: function(elem){ - return /h\d/i.test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; - }, - input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); + + break; } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; - }, - gt: function(elem, i, match){ - return i > match[3] - 0; - }, - nth: function(elem, i, match){ - return match[3] - 0 === i; - }, - eq: function(elem, i, match){ - return match[3] - 0 === i; + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + GQS.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +GQS.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = GQS.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = $wnd.Expr.filters[ name ]; - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || @com.google.gwt.query.client.impl.SelectorEngineSizzle::getText(Ljava/lang/Object;)([ elem ]) || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; - - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { - return false; - } - } - return true; - } else { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::error(Ljava/lang/String;)("Syntax error, unrecognized expression: " + name); - } - }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - if ( type === "first" ) { - return true; - } - node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - return true; - case 'nth': - var first = match[2], last = match[3]; - if ( first === 1 && last === 0 ) { - return true; - } - var doneName = match[0], - parent = elem.parentNode; - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - var diff = elem.nodeIndex - last; - if ( first === 0 ) { - return diff === 0; - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } + } + + if ( isPartStrNotTag ) { + GQS.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string", + elem, i = 0, l = checkSet.length; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } - }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = $wnd.Expr.attrHandle[ name ] ? - $wnd.Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = $wnd.Expr.setFilters[ name ]; - if ( filter ) { - return filter( elem, i, match, array ); + } + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; } } + + if ( isPartStr ) { + GQS.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; } - }; - for ( var type in $wnd.Expr.match ) { - $wnd.Expr.match[ type ] = new RegExp( $wnd.Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); - $wnd.Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + $wnd.Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ - return "\\" + (num - 0 + 1); - })); + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); } - - return $wnd.Expr; - }-*/; + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); - public static native void dirCheck(String dir, Object cur, int doneName, Object checkSet) /*-{ - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - if ( elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - } else if ( @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( cur, [elem], false ).length > 0 ) { - match = elem; - break; - } - } - elem = elem[dir]; - } - checkSet[i] = match; - } - } - }-*/; - - public static native void dirNodeCheck(String dir, Object cur, int doneName, Object checkSet) /*-{ - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); } - if ( elem.nodeType === 1){ - elem.sizcache = doneName; - elem.sizset = i; - } - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - elem = elem[dir]; } - checkSet[i] = match; + + return ret.length === 0 ? null : ret; } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); } - }-*/; - - public static void error(String msg) { - throw new IllegalArgumentException("Syntax error, unrecognized expression: " + msg); - } - - public static native JsArray filter(String expr, JsArray set, boolean inplace, Object not) /*-{ - var old = expr, result = [], curLoop = set, match, anyFound; - while ( expr && set.length ) { - for ( var type in $wnd.Expr.filter ) { - if ( (match = $wnd.Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var filter = $wnd.Expr.filter[ type ], found, item, left = match[1]; - anyFound = false; - match.splice(1,1); - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - if ( curLoop === result ) { - result = []; - } - if ( $wnd.Expr.preFilter[ type ] ) { - match = $wnd.Expr.preFilter[ type ]( match, curLoop, inplace, result, not); - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - if ( found !== undefined ) { + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { - curLoop = result; + result.push( elem ); } - expr = expr.replace( $wnd.Expr.match[ type ], "" ); - if ( !anyFound ) { - return []; - } - break; + } else if ( inplace ) { + curLoop[i] = false; } } } - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::error(Ljava/lang/String;)(expr); - } else { - break; - } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; } - old = expr; - } - return curLoop; - }-*/; - public static native JavaScriptObject find(String expr, Node context) /*-{ - var set, match; - if ( !expr ) { - return []; - } - for ( var i = 0, l = $wnd.Expr.order.length; i < l; i++ ) { - var type = $wnd.Expr.order[i], match; + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); - if ( (match = $wnd.Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice(1,1); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = $wnd.Expr.find[ type ]( match, context); - if ( set != null ) { - expr = expr.replace( $wnd.Expr.match[ type ], "" ); - break; + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = GQS(match[3], null, null, curLoop); + } else { + var ret = GQS.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); } + return false; } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; } - if ( !set ) { - set = context.getElementsByTagName("*"); + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!GQS( match[3], elem ).length; + }, + header: function(elem){ + return (/h\d/i).test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return (/input|select|textarea|button/i).test(elem.nodeName); } - return {set: set, expr: expr}; - }-*/; - - public static native String getText(Object elems) /*-{ - var ret = "", elem; - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += @com.google.gwt.query.client.impl.SelectorEngineSizzle::getText(Ljava/lang/Object;)(elem.childNodes); + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || GQS.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + } else { + GQS.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); } } - return ret; - }-*/; - - public static native JsArray makeArray(NodeList array, JsArray results) /*-{ - var ret = results || []; - if ( Object.prototype.toString.call(array) === "[object Array]" ) { + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || [], i = 0; + + if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { + for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { - for ( var i = 0; array[i]; i++ ) { + for ( ; array[i]; i++ ) { ret.push( array[i] ); } } } - return ret; - }-*/; - - public static native JsArray posProcess(String selector, Node context) /*-{ - var tmpSet = [], later = "", match, root = context.nodeType ? [context] : context; - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = $wnd.Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace($wnd.Expr.match.PSEUDO, "" ); + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; } - selector = $wnd.Expr.relative[selector] ? selector + "*" : selector; - for ( var i = 0, l = root.length; i < l; i++ ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(selector, root[i], tmpSet, null); + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; } - return @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( later, tmpSet, false ); - }-*/; - - private static native JsArray select(String selector, Node context, JsArray results, JsArray seed) /*-{ - results = results || []; - var origContext = context = context || document; - var parts = [], m, set, checkSet, extra, prune = true, soFar = selector; - // Reset the position of the chunker regexp (start from head) - while ( ($wnd.Expr.match.CHUNKER.exec(""), m = $wnd.Expr.match.CHUNKER.exec(soFar)) !== null ) { - soFar = m[3]; - parts.push( m[1] ); - if ( m[2] ) { - extra = m[3]; - break; + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; } + return a.sourceIndex ? -1 : 1; } - if ( parts.length > 1 && $wnd.Expr.match.POS.exec( selector ) ) { - if ( parts.length === 2 && $wnd.Expr.relative[ parts[0] ] ) { - set = @com.google.gwt.query.client.impl.SelectorEngineSizzle::posProcess(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)(parts[0] + parts[1], context); - } else { - set = $wnd.Expr.relative[ parts[0] ] ? - [ context ] : - @com.google.gwt.query.client.impl.SelectorEngineSizzle::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(parts.shift(), context, null, null); - while ( parts.length ) { - selector = parts.shift(); - if ( $wnd.Expr.relative[ selector ] ) { - selector += parts.shift(); - } - set = @com.google.gwt.query.client.impl.SelectorEngineSizzle::posProcess(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)(selector, set); - } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; } - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && - $wnd.Expr.match.ID.test(parts[0]) && !$wnd.Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = @com.google.gwt.query.client.impl.SelectorEngineSizzle::find(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)( parts.shift(), context); - context = ret.expr ? @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( ret.expr, ret.set, false )[0] : ret.set[0]; - } - if ( context ) { - var ret = seed ? - { expr: parts.pop(), set: @com.google.gwt.query.client.impl.SelectorEngineSizzle::makeArray(Lcom/google/gwt/dom/client/NodeList;Lcom/google/gwt/core/client/JsArray;)(seed, null) } : - @com.google.gwt.query.client.impl.SelectorEngineSizzle::find(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context); - set = ret.expr ? @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( ret.expr, ret.set, false ) : ret.set; - if ( parts.length > 0 ) { - checkSet = @com.google.gwt.query.client.impl.SelectorEngineSizzle::makeArray(Lcom/google/gwt/dom/client/NodeList;Lcom/google/gwt/core/client/JsArray;)(set, null); - } else { - prune = false; - } - while ( parts.length ) { - var cur = parts.pop(), pop = cur; - if ( !$wnd.Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - if ( pop == null ) { - pop = context; + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +GQS.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += GQS.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); } - $wnd.Expr.relative[ cur ]( checkSet, pop); } - } else { - checkSet = parts = []; + + results = tmp; } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldGQS = GQS, div = document.createElement("div"); + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; } - if ( !checkSet ) { - checkSet = set; + + GQS = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !GQS.isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldGQS(query, context, extra, seed); + }; + + for ( var prop in oldGQS ) { + GQS[ prop ] = oldGQS[ prop ]; } - if ( !checkSet ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::error(Ljava/lang/String;)(cur || selector); + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); } - if ( Object.prototype.toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && @com.google.gwt.query.client.impl.SelectorEngineSizzle::contains(Ljava/lang/Object;Ljava/lang/Object;)(context, checkSet[i])) ) { - results.push( set[i] ); - } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; } - } else { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; } - } else { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::makeArray(Lcom/google/gwt/dom/client/NodeList;Lcom/google/gwt/core/client/JsArray;)(checkSet, results); + + checkSet[i] = match; } - if ( extra ) { - @com.google.gwt.query.client.impl.SelectorEngineSizzle::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(extra, origContext, results, seed); + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( GQS.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; } - return results; - }-*/; + } +} + +GQS.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +GQS.isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + GQS( selector, root[i], tmpSet ); + } + + return GQS.filter( later, tmpSet ); +}; + +// EXPOSE + +window.GQS = GQS; +$wnd.GQS = GQS; + +})(); + + }-*/; + + + private static native JsArray select(String selector, Node context, JsArray results, JsArray seed) /*-{ + return $wnd.GQS(selector, context, results, seed); + }-*/; + + static boolean initialized = false; public SelectorEngineSizzle() { - createExpr(); + if (!initialized) { + initialize(); + } } public NodeList select(String selector, Node context) { JsArray results = JavaScriptObject.createArray().cast(); - return GQUtils.unique(select(selector, context, results, null)).cast(); + return select(selector, context, results, null).cast(); } } diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzleGwt.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzleGwt.java new file mode 100644 index 00000000..44780ea9 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzleGwt.java @@ -0,0 +1,738 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.query.client.impl; + + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.query.client.GQUtils; + +/** + * Pure Javascript Selector Engine Gwt Implementation based on + * Sizzle CSS Selector Engine v1.0. + * + * It has so many JSNI code, the idea is to make an entire implementation + * using Java. Right now it performs worse than pure JSNI implementation + * + */ +public class SelectorEngineSizzleGwt extends SelectorEngineImpl { + + public static native boolean contains(Object a, Object b) /*-{ + var ret = + document.compareDocumentPosition ? + (a.compareDocumentPosition(b) & 16): + a !== b && (a.contains ? a.contains(b) : true); + return ret ? true : false; + }-*/; + + public static native JavaScriptObject createExpr() /*-{ + var done = 0; + $wnd.Expr = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/, + CHUNKER: /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + if ( isTag ) { + part = part.toLowerCase(); + } + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + if ( isPartStrNotTag ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + if ( isPartStr ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( part, checkSet, true ); + } + } + }, + "": function(checkSet, part){ + var doneName = done++; + if ( typeof part === "string" && !/\W/.test(part) ) { + checkFn = $wnd.dirNodeCheck; + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::dirNodeCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("parentNode", part, doneName, checkSet); + } else { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::dirCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("parentNode", part, doneName, checkSet); + } + }, + "~": function(checkSet, part){ + var doneName = done++; + if ( typeof part === "string" && !/\W/.test(part) ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::dirNodeCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("previousSibling", part, doneName, checkSet); + } else { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::dirCheck(Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)("previousSibling", part, doneName, checkSet); + } + } + }, + find: { + ID: function(match, context){ + if ( typeof context.getElementById !== "undefined") { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not){ + match = " " + match[1].replace(/\\/g, "") + " "; + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + match[0] = done++; + return match; + }, + ATTR: function(match, curLoop, inplace, result, not){ + var name = match[1].replace(/\\/g, ""); + if ($wnd.Expr.attrMap[name] ) { + match[1] = $wnd.Expr.attrMap[name]; + } + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( $wnd.Expr.match.CHUNKER.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(match[3], null, null, curLoop); + } else { + var ret = @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( $wnd.Expr.match.POS.test( match[0] ) || $wnd.Expr.match.CHILD.test( match[0] ) ) { + return true; + } + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!@com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(match[3], elem, null, null).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = $wnd.Expr.filters[ name ]; + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::getText(Ljava/lang/Object;)([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + return true; + } else { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::error(Ljava/lang/String;)("Syntax error, unrecognized expression: " + name); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + if ( first === 1 && last === 0 ) { + return true; + } + var doneName = match[0], + parent = elem.parentNode; + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = $wnd.Expr.attrHandle[ name ] ? + $wnd.Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = $wnd.Expr.setFilters[ name ]; + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } + }; + + for ( var type in $wnd.Expr.match ) { + $wnd.Expr.match[ type ] = new RegExp( $wnd.Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + $wnd.Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + $wnd.Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); + } + + return $wnd.Expr; + }-*/; + + public static native void dirCheck(String dir, Object cur, int doneName, Object checkSet) /*-{ + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + if ( elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + } else if ( @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( cur, [elem], false ).length > 0 ) { + match = elem; + break; + } + } + elem = elem[dir]; + } + checkSet[i] = match; + } + } + }-*/; + + public static native void dirNodeCheck(String dir, Object cur, int doneName, Object checkSet) /*-{ + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + if ( elem.nodeType === 1){ + elem.sizcache = doneName; + elem.sizset = i; + } + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + elem = elem[dir]; + } + checkSet[i] = match; + } + } + }-*/; + + public static void error(String msg) { + throw new IllegalArgumentException("Syntax error, unrecognized expression: " + msg); + } + + public static native JsArray filter(String expr, JsArray set, boolean inplace, Object not) /*-{ + var old = expr, result = [], curLoop = set, match, anyFound; + while ( expr && set.length ) { + for ( var type in $wnd.Expr.filter ) { + if ( (match = $wnd.Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = $wnd.Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + match.splice(1,1); + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + if ( curLoop === result ) { + result = []; + } + if ( $wnd.Expr.preFilter[ type ] ) { + match = $wnd.Expr.preFilter[ type ]( match, curLoop, inplace, result, not); + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + expr = expr.replace( $wnd.Expr.match[ type ], "" ); + if ( !anyFound ) { + return []; + } + break; + } + } + } + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::error(Ljava/lang/String;)(expr); + } else { + break; + } + } + old = expr; + } + return curLoop; + }-*/; + + public static native JavaScriptObject find(String expr, Node context) /*-{ + var set, match; + if ( !expr ) { + return []; + } + for ( var i = 0, l = $wnd.Expr.order.length; i < l; i++ ) { + var type = $wnd.Expr.order[i], match; + + if ( (match = $wnd.Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = $wnd.Expr.find[ type ]( match, context); + if ( set != null ) { + expr = expr.replace( $wnd.Expr.match[ type ], "" ); + break; + } + } + } + } + if ( !set ) { + set = context.getElementsByTagName("*"); + } + return {set: set, expr: expr}; + }-*/; + + public static native String getText(Object elems) /*-{ + var ret = "", elem; + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::getText(Ljava/lang/Object;)(elem.childNodes); + } + } + return ret; + }-*/; + + public static native JsArray makeArray(NodeList array, JsArray results) /*-{ + var ret = results || []; + if ( Object.prototype.toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + return ret; + }-*/; + + public static native JsArray posProcess(String selector, Node context) /*-{ + var tmpSet = [], later = "", match, root = context.nodeType ? [context] : context; + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = $wnd.Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace($wnd.Expr.match.PSEUDO, "" ); + } + selector = $wnd.Expr.relative[selector] ? selector + "*" : selector; + for ( var i = 0, l = root.length; i < l; i++ ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(selector, root[i], tmpSet, null); + } + return @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( later, tmpSet, false ); + }-*/; + + private static native JsArray select(String selector, Node context, JsArray results, JsArray seed) /*-{ + results = results || []; + var origContext = context = context || document; + var parts = [], m, set, checkSet, extra, prune = true, soFar = selector; + // Reset the position of the chunker regexp (start from head) + while ( ($wnd.Expr.match.CHUNKER.exec(""), m = $wnd.Expr.match.CHUNKER.exec(soFar)) !== null ) { + soFar = m[3]; + parts.push( m[1] ); + if ( m[2] ) { + extra = m[3]; + break; + } + } + if ( parts.length > 1 && $wnd.Expr.match.POS.exec( selector ) ) { + if ( parts.length === 2 && $wnd.Expr.relative[ parts[0] ] ) { + set = @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::posProcess(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)(parts[0] + parts[1], context); + } else { + set = $wnd.Expr.relative[ parts[0] ] ? + [ context ] : + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(parts.shift(), context, null, null); + while ( parts.length ) { + selector = parts.shift(); + if ( $wnd.Expr.relative[ selector ] ) { + selector += parts.shift(); + } + set = @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::posProcess(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)(selector, set); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && + $wnd.Expr.match.ID.test(parts[0]) && !$wnd.Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::find(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)( parts.shift(), context); + context = ret.expr ? @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( ret.expr, ret.set, false )[0] : ret.set[0]; + } + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::makeArray(Lcom/google/gwt/dom/client/NodeList;Lcom/google/gwt/core/client/JsArray;)(seed, null) } : + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::find(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;)( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context); + set = ret.expr ? @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( ret.expr, ret.set, false ) : ret.set; + if ( parts.length > 0 ) { + checkSet = @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::makeArray(Lcom/google/gwt/dom/client/NodeList;Lcom/google/gwt/core/client/JsArray;)(set, null); + } else { + prune = false; + } + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + if ( !$wnd.Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + if ( pop == null ) { + pop = context; + } + $wnd.Expr.relative[ cur ]( checkSet, pop); + } + } else { + checkSet = parts = []; + } + } + if ( !checkSet ) { + checkSet = set; + } + if ( !checkSet ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::error(Ljava/lang/String;)(cur || selector); + } + if ( Object.prototype.toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::contains(Ljava/lang/Object;Ljava/lang/Object;)(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::makeArray(Lcom/google/gwt/dom/client/NodeList;Lcom/google/gwt/core/client/JsArray;)(checkSet, results); + } + if ( extra ) { + @com.google.gwt.query.client.impl.SelectorEngineSizzleGwt::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(extra, origContext, results, seed); + } + return results; + }-*/; + + public SelectorEngineSizzleGwt() { + createExpr(); + } + + public NodeList select(String selector, Node context) { + JsArray results = JavaScriptObject.createArray().cast(); + return GQUtils.unique(select(selector, context, results, null)).cast(); + } +} diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java index b30d12c1..b6415e0a 100644 --- a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java @@ -16,10 +16,11 @@ package com.google.gwt.query.client; import static com.google.gwt.query.client.GQuery.$; +import static com.google.gwt.query.client.GQuery.body; +import static com.google.gwt.query.client.GQuery.document; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArray; -import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; @@ -29,6 +30,7 @@ import com.google.gwt.query.client.impl.SelectorEngineImpl; import com.google.gwt.query.client.impl.SelectorEngineJS; import com.google.gwt.query.client.impl.SelectorEngineNative; import com.google.gwt.query.client.impl.SelectorEngineSizzle; +import com.google.gwt.query.client.impl.SelectorEngineSizzleGwt; import com.google.gwt.query.client.impl.SelectorEngineXPath; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; @@ -130,10 +132,19 @@ public class GQuerySelectorsTest extends GWTTestCase { GQuery branchA(); @Selector(".branchA") GQuery branchA(Node n); + @Selector(".branchA .target") + GQuery branchAtarget(); + @Selector(".branchA .target") + GQuery branchAtarget(Node n); @Selector(".branchB") GQuery branchB(); @Selector(".branchB") GQuery branchB(Node n); + + @Selector("div .target") + GQuery divTarget(); + @Selector("div .target") + GQuery divTarget(Node n); @Selector(".target") GQuery target(); @Selector(".target") @@ -143,6 +154,10 @@ public class GQuerySelectorsTest extends GWTTestCase { static Element e = null; static HTML testPanel = null; + private static native boolean hasNativeSelector() /*-{ + return !!(document.querySelectorAll && /native/.test(String(document.querySelectorAll))); + }-*/; + public String getModuleName() { return "com.google.gwt.query.Query"; } @@ -206,7 +221,7 @@ public class GQuerySelectorsTest extends GWTTestCase { assertArrayContains(sel.ulTocline2().getLength(), 12); assertArrayContains(sel.ulTocLiTocLine2().getLength(), 12); } - + public void testIssue12() { $(e).html("

1

2
"); executeSelectInAllImplementations(":checked", e, 1); @@ -218,22 +233,22 @@ public class GQuerySelectorsTest extends GWTTestCase { executeSelectInAllImplementations("input[name='wantedName']", e, 1); executeSelectInAllImplementations("input[name=\"wantedName\"]", e, 1); } - + public void testSelectElementsInsideContext() { $(e).html("

s

"); GQuery q = $("spam", e); - // TODO: in XPath engine it returns 2 when it should return 1 + // TODO: in XPath engine returns 2 when it should return 1 executeSelectInAllImplementations("*", q.get(0), 1, 2); } - public void testSelectorEngineDomAssistant() { - // This test runs very slow in chrome - SelectorEngineImpl selEng = new SelectorEngineJS(); + public void testSelectorEngineCssToXpath() { + SelectorEngineImpl selEng = new SelectorEngineCssToXPath(); executeSelectorEngineTests(selEng); } - public void testSelectorEngineSizzle() { - SelectorEngineImpl selEng = new SelectorEngineSizzle(); + public void testSelectorEngineDomAssistant() { + // This test runs very slow in chrome + SelectorEngineImpl selEng = new SelectorEngineJS(); executeSelectorEngineTests(selEng); } @@ -244,16 +259,21 @@ public class GQuerySelectorsTest extends GWTTestCase { } } - public void testSelectorEngineXpath() { - SelectorEngineImpl selEng = new SelectorEngineXPath(); + public void testSelectorEngineSizzle() { + SelectorEngineImpl selEng = new SelectorEngineSizzle(); executeSelectorEngineTests(selEng); } - public void testSelectorEngineCssToXpath() { - SelectorEngineImpl selEng = new SelectorEngineCssToXPath(); + public void testSelectorEngineSizzleGwt() { + SelectorEngineImpl selEng = new SelectorEngineSizzleGwt(); executeSelectorEngineTests(selEng); } + public void testSelectorEngineXpath() { + SelectorEngineImpl selEng = new SelectorEngineXPath(); + executeSelectorEngineTests(selEng); + } + public void testSelectorsGeneratorNative() { $(e).html( "1" @@ -263,6 +283,29 @@ public class GQuerySelectorsTest extends GWTTestCase { assertEquals(1, selectors.allChecked().size()); } + public void testSelectorsInIframe() { + $(e).html("