query.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558
  1. // Copyright 2005-2009, The Dojo Foundation
  2. // Modifications Copyright 2008 The Closure Library Authors.
  3. // All Rights Reserved.
  4. /**
  5. * @license Portions of this code are from the Dojo Toolkit, received by
  6. * The Closure Library Authors under the BSD license. All other code is
  7. * Copyright 2005-2009 The Closure Library Authors. All Rights Reserved.
  8. The "New" BSD License:
  9. Copyright (c) 2005-2009, The Dojo Foundation
  10. All rights reserved.
  11. Redistribution and use in source and binary forms, with or without
  12. modification, are permitted provided that the following conditions are met:
  13. * Redistributions of source code must retain the above copyright notice, this
  14. list of conditions and the following disclaimer.
  15. * Redistributions in binary form must reproduce the above copyright notice,
  16. this list of conditions and the following disclaimer in the documentation
  17. and/or other materials provided with the distribution.
  18. * Neither the name of the Dojo Foundation nor the names of its contributors
  19. may be used to endorse or promote products derived from this software
  20. without specific prior written permission.
  21. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  22. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  23. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  24. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
  25. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  26. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  27. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  29. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. /**
  33. * @fileoverview This code was ported from the Dojo Toolkit
  34. http://dojotoolkit.org and modified slightly for Closure.
  35. *
  36. * goog.dom.query is a relatively full-featured CSS3 query function. It is
  37. * designed to take any valid CSS3 selector and return the nodes matching
  38. * the selector. To do this quickly, it processes queries in several
  39. * steps, applying caching where profitable.
  40. * The steps (roughly in reverse order of the way they appear in the code):
  41. * 1.) check to see if we already have a "query dispatcher"
  42. * - if so, use that with the given parameterization. Skip to step 4.
  43. * 2.) attempt to determine which branch to dispatch the query to:
  44. * - JS (optimized DOM iteration)
  45. * - native (FF3.1, Safari 3.2+, Chrome, some IE 8 doctypes). If native,
  46. * skip to step 4, using a stub dispatcher for QSA queries.
  47. * 3.) tokenize and convert to executable "query dispatcher"
  48. * assembled as a chain of "yes/no" test functions pertaining to
  49. * a section of a simple query statement (".blah:nth-child(odd)"
  50. * but not "div div", which is 2 simple statements).
  51. * 4.) the resulting query dispatcher is called in the passed scope
  52. * (by default the top-level document)
  53. * - for DOM queries, this results in a recursive, top-down
  54. * evaluation of nodes based on each simple query section
  55. * - querySelectorAll is used instead of DOM where possible. If a query
  56. * fails in this mode, it is re-run against the DOM evaluator and all
  57. * future queries using the same selector evaluate against the DOM branch
  58. * too.
  59. * 5.) matched nodes are pruned to ensure they are unique
  60. * @deprecated This is an all-software query selector. When developing for
  61. * recent browsers, use document.querySelector. See information at
  62. * http://caniuse.com/queryselector and
  63. * https://developer.mozilla.org/en-US/docs/DOM/Document.querySelector .
  64. */
  65. goog.provide('goog.dom.query');
  66. goog.require('goog.array');
  67. goog.require('goog.dom');
  68. goog.require('goog.functions');
  69. goog.require('goog.string');
  70. goog.require('goog.userAgent');
  71. /**
  72. * Returns nodes which match the given CSS3 selector, searching the
  73. * entire document by default but optionally taking a node to scope
  74. * the search by.
  75. *
  76. * dojo.query() is the swiss army knife of DOM node manipulation in
  77. * Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
  78. * "$" function, dojo.query provides robust, high-performance
  79. * CSS-based node selector support with the option of scoping searches
  80. * to a particular sub-tree of a document.
  81. *
  82. * Supported Selectors:
  83. * --------------------
  84. *
  85. * dojo.query() supports a rich set of CSS3 selectors, including:
  86. *
  87. * * class selectors (e.g., `.foo`)
  88. * * node type selectors like `span`
  89. * * ` ` descendant selectors
  90. * * `>` child element selectors
  91. * * `#foo` style ID selectors
  92. * * `*` universal selector
  93. * * `~`, the immediately preceded-by sibling selector
  94. * * `+`, the preceded-by sibling selector
  95. * * attribute queries:
  96. * * `[foo]` attribute presence selector
  97. * * `[foo='bar']` attribute value exact match
  98. * * `[foo~='bar']` attribute value list item match
  99. * * `[foo^='bar']` attribute start match
  100. * * `[foo$='bar']` attribute end match
  101. * * `[foo*='bar']` attribute substring match
  102. * * `:first-child`, `:last-child` positional selectors
  103. * * `:empty` content empty selector
  104. * * `:empty` content empty selector
  105. * * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
  106. * * `:nth-child(even)`, `:nth-child(odd)` positional selectors
  107. * * `:not(...)` negation pseudo selectors
  108. *
  109. * Any legal combination of these selectors will work with
  110. * `dojo.query()`, including compound selectors ("," delimited).
  111. * Very complex and useful searches can be constructed with this
  112. * palette of selectors.
  113. *
  114. * Unsupported Selectors:
  115. * ----------------------
  116. *
  117. * While dojo.query handles many CSS3 selectors, some fall outside of
  118. * what's reasonable for a programmatic node querying engine to
  119. * handle. Currently unsupported selectors include:
  120. *
  121. * * namespace-differentiated selectors of any form
  122. * * all `::` pseudo-element selectors
  123. * * certain pseudo-selectors which don't get a lot of day-to-day use:
  124. * * `:root`, `:lang()`, `:target`, `:focus`
  125. * * all visual and state selectors:
  126. * * `:root`, `:active`, `:hover`, `:visited`, `:link`,
  127. * `:enabled`, `:disabled`, `:checked`
  128. * * `:*-of-type` pseudo selectors
  129. *
  130. * dojo.query and XML Documents:
  131. * -----------------------------
  132. *
  133. * `dojo.query` currently only supports searching XML documents
  134. * whose tags and attributes are 100% lower-case. This is a known
  135. * limitation and will [be addressed soon]
  136. * (http://trac.dojotoolkit.org/ticket/3866)
  137. *
  138. * Non-selector Queries:
  139. * ---------------------
  140. *
  141. * If something other than a String is passed for the query,
  142. * `dojo.query` will return a new array constructed from
  143. * that parameter alone and all further processing will stop. This
  144. * means that if you have a reference to a node or array or nodes, you
  145. * can quickly construct a new array of nodes from the original by
  146. * calling `dojo.query(node)` or `dojo.query(array)`.
  147. *
  148. * __Example:__ search the entire document for elements with the class "foo":
  149. *
  150. * dojo.query(".foo");
  151. *
  152. * these elements will match:
  153. *
  154. * <span class="foo"></span>
  155. * <span class="foo bar"></span>
  156. * <p class="thud foo"></p>
  157. *
  158. * __Example:__ search the entire document for elements with the classes "foo"
  159. * _and_ "bar":
  160. *
  161. * dojo.query(".foo.bar");
  162. *
  163. * these elements will match:
  164. *
  165. * <span class="foo bar"></span>
  166. *
  167. * while these will not:
  168. *
  169. * <span class="foo"></span>
  170. * <p class="thud foo"></p>
  171. *
  172. * __Example:__ find `<span>` elements which are descendants of paragraphs and
  173. * which have a "highlighted" class:
  174. *
  175. * dojo.query("p span.highlighted");
  176. *
  177. * the innermost span in this fragment matches:
  178. *
  179. * <p class="foo">
  180. * <span>...
  181. * <span class="highlighted foo bar">...</span>
  182. * </span>
  183. * </p>
  184. *
  185. * __Example:__ find all odd table rows inside of the table `#tabular_data`,
  186. * using the `>` (direct child) selector to avoid affecting any nested tables:
  187. *
  188. * dojo.query("#tabular_data > tbody > tr:nth-child(odd)");
  189. *
  190. * @param {string|Array} query The CSS3 expression to match against.
  191. * For details on the syntax of CSS3 selectors, see
  192. * http://www.w3.org/TR/css3-selectors/#selectors.
  193. * @param {(string|Node)=} opt_root A Node (or node id) to scope the search
  194. * from (optional).
  195. * @return { {length: number} } The elements that matched the query.
  196. *
  197. * @deprecated This is an all-software query selector. Use
  198. * document.querySelector. See
  199. * https://developer.mozilla.org/en-US/docs/DOM/Document.querySelector .
  200. */
  201. goog.dom.query = (function() {
  202. ////////////////////////////////////////////////////////////////////////
  203. // Global utilities
  204. ////////////////////////////////////////////////////////////////////////
  205. var cssCaseBug = (goog.userAgent.WEBKIT &&
  206. ((goog.dom.getDocument().compatMode) == 'BackCompat')
  207. );
  208. var legacyIE = goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9');
  209. // On browsers that support the "children" collection we can avoid a lot of
  210. // iteration on chaff (non-element) nodes.
  211. var childNodesName =
  212. goog.dom.getDocument().firstChild['children'] ? 'children' : 'childNodes';
  213. var specials = '>~+';
  214. // Global thunk to determine whether we should treat the current query as
  215. // case sensitive or not. This switch is flipped by the query evaluator based
  216. // on the document passed as the context to search.
  217. var caseSensitive = false;
  218. ////////////////////////////////////////////////////////////////////////
  219. // Tokenizer
  220. ////////////////////////////////////////////////////////////////////////
  221. var getQueryParts = function(query) {
  222. // summary:
  223. // state machine for query tokenization
  224. // description:
  225. // instead of using a brittle and slow regex-based CSS parser,
  226. // dojo.query implements an AST-style query representation. This
  227. // representation is only generated once per query. For example,
  228. // the same query run multiple times or under different root nodes
  229. // does not re-parse the selector expression but instead uses the
  230. // cached data structure. The state machine implemented here
  231. // terminates on the last " " (space) character and returns an
  232. // ordered array of query component structures (or "parts"). Each
  233. // part represents an operator or a simple CSS filtering
  234. // expression. The structure for parts is documented in the code
  235. // below.
  236. // NOTE:
  237. // this code is designed to run fast and compress well. Sacrifices
  238. // to readability and maintainability have been made.
  239. if (specials.indexOf(query.slice(-1)) >= 0) {
  240. // If we end with a ">", "+", or "~", that means we're implicitly
  241. // searching all children, so make it explicit.
  242. query += ' * ';
  243. } else {
  244. // if you have not provided a terminator, one will be provided for
  245. // you...
  246. query += ' ';
  247. }
  248. var ts = function(/*Integer*/ s, /*Integer*/ e) {
  249. // trim and slice.
  250. // take an index to start a string slice from and an end position
  251. // and return a trimmed copy of that sub-string
  252. return goog.string.trim(query.slice(s, e));
  253. };
  254. // The overall data graph of the full query, as represented by queryPart
  255. // objects.
  256. var queryParts = [];
  257. // state keeping vars
  258. var inBrackets = -1,
  259. inParens = -1,
  260. inMatchFor = -1,
  261. inPseudo = -1,
  262. inClass = -1,
  263. inId = -1,
  264. inTag = -1,
  265. lc = '',
  266. cc = '',
  267. pStart;
  268. // iteration vars
  269. var x = 0, // index in the query
  270. ql = query.length,
  271. currentPart = null, // data structure representing the entire clause
  272. cp = null; // the current pseudo or attr matcher
  273. // several temporary variables are assigned to this structure during a
  274. // potential sub-expression match:
  275. // attr:
  276. // a string representing the current full attribute match in a
  277. // bracket expression
  278. // type:
  279. // if there's an operator in a bracket expression, this is
  280. // used to keep track of it
  281. // value:
  282. // the internals of parenthetical expression for a pseudo. for
  283. // :nth-child(2n+1), value might be '2n+1'
  284. var endTag = function() {
  285. // called when the tokenizer hits the end of a particular tag name.
  286. // Re-sets state variables for tag matching and sets up the matcher
  287. // to handle the next type of token (tag or operator).
  288. if (inTag >= 0) {
  289. var tv = (inTag == x) ? null : ts(inTag, x);
  290. if (specials.indexOf(tv) < 0) {
  291. currentPart.tag = tv;
  292. } else {
  293. currentPart.oper = tv;
  294. }
  295. inTag = -1;
  296. }
  297. };
  298. var endId = function() {
  299. // Called when the tokenizer might be at the end of an ID portion of a
  300. // match.
  301. if (inId >= 0) {
  302. currentPart.id = ts(inId, x).replace(/\\/g, '');
  303. inId = -1;
  304. }
  305. };
  306. var endClass = function() {
  307. // Called when the tokenizer might be at the end of a class name
  308. // match. CSS allows for multiple classes, so we augment the
  309. // current item with another class in its list.
  310. if (inClass >= 0) {
  311. currentPart.classes.push(ts(inClass + 1, x).replace(/\\/g, ''));
  312. inClass = -1;
  313. }
  314. };
  315. var endAll = function() {
  316. // at the end of a simple fragment, so wall off the matches
  317. endId(); endTag(); endClass();
  318. };
  319. var endPart = function() {
  320. endAll();
  321. if (inPseudo >= 0) {
  322. currentPart.pseudos.push({ name: ts(inPseudo + 1, x) });
  323. }
  324. // Hint to the selector engine to tell it whether or not it
  325. // needs to do any iteration. Many simple selectors don't, and
  326. // we can avoid significant construction-time work by advising
  327. // the system to skip them.
  328. currentPart.loops = currentPart.pseudos.length ||
  329. currentPart.attrs.length ||
  330. currentPart.classes.length;
  331. // save the full expression as a string
  332. currentPart.oquery = currentPart.query = ts(pStart, x);
  333. // otag/tag are hints to suggest to the system whether or not
  334. // it's an operator or a tag. We save a copy of otag since the
  335. // tag name is cast to upper-case in regular HTML matches. The
  336. // system has a global switch to figure out if the current
  337. // expression needs to be case sensitive or not and it will use
  338. // otag or tag accordingly
  339. currentPart.otag = currentPart.tag = (currentPart.oper) ?
  340. null :
  341. (currentPart.tag || '*');
  342. if (currentPart.tag) {
  343. // if we're in a case-insensitive HTML doc, we likely want
  344. // the toUpperCase when matching on element.tagName. If we
  345. // do it here, we can skip the string op per node
  346. // comparison
  347. currentPart.tag = currentPart.tag.toUpperCase();
  348. }
  349. // add the part to the list
  350. if (queryParts.length && (queryParts[queryParts.length - 1].oper)) {
  351. // operators are always infix, so we remove them from the
  352. // list and attach them to the next match. The evaluator is
  353. // responsible for sorting out how to handle them.
  354. currentPart.infixOper = queryParts.pop();
  355. currentPart.query = currentPart.infixOper.query + ' ' +
  356. currentPart.query;
  357. }
  358. queryParts.push(currentPart);
  359. currentPart = null;
  360. };
  361. // iterate over the query, character by character, building up a
  362. // list of query part objects
  363. for (; lc = cc, cc = query.charAt(x), x < ql; x++) {
  364. // cc: the current character in the match
  365. // lc: the last character (if any)
  366. // someone is trying to escape something, so don't try to match any
  367. // fragments. We assume we're inside a literal.
  368. if (lc == '\\') {
  369. continue;
  370. }
  371. if (!currentPart) { // a part was just ended or none has yet been created
  372. // NOTE: I hate all this alloc, but it's shorter than writing tons of
  373. // if's
  374. pStart = x;
  375. // rules describe full CSS sub-expressions, like:
  376. // #someId
  377. // .className:first-child
  378. // but not:
  379. // thinger > div.howdy[type=thinger]
  380. // the individual components of the previous query would be
  381. // split into 3 parts that would be represented a structure
  382. // like:
  383. // [
  384. // {
  385. // query: 'thinger',
  386. // tag: 'thinger',
  387. // },
  388. // {
  389. // query: 'div.howdy[type=thinger]',
  390. // classes: ['howdy'],
  391. // infixOper: {
  392. // query: '>',
  393. // oper: '>',
  394. // }
  395. // },
  396. // ]
  397. currentPart = {
  398. query: null, // the full text of the part's rule
  399. pseudos: [], // CSS supports multiple pseudo-class matches in a single
  400. // rule
  401. attrs: [], // CSS supports multi-attribute match, so we need an array
  402. classes: [], // class matches may be additive,
  403. // e.g.: .thinger.blah.howdy
  404. tag: null, // only one tag...
  405. oper: null, // ...or operator per component. Note that these wind up
  406. // being exclusive.
  407. id: null, // the id component of a rule
  408. getTag: function() {
  409. return (caseSensitive) ? this.otag : this.tag;
  410. }
  411. };
  412. // if we don't have a part, we assume we're going to start at
  413. // the beginning of a match, which should be a tag name. This
  414. // might fault a little later on, but we detect that and this
  415. // iteration will still be fine.
  416. inTag = x;
  417. }
  418. if (inBrackets >= 0) {
  419. // look for a the close first
  420. if (cc == ']') { // if we're in a [...] clause and we end, do assignment
  421. if (!cp.attr) {
  422. // no attribute match was previously begun, so we
  423. // assume this is an attribute existence match in the
  424. // form of [someAttributeName]
  425. cp.attr = ts(inBrackets + 1, x);
  426. } else {
  427. // we had an attribute already, so we know that we're
  428. // matching some sort of value, as in [attrName=howdy]
  429. cp.matchFor = ts((inMatchFor || inBrackets + 1), x);
  430. }
  431. var cmf = cp.matchFor;
  432. if (cmf) {
  433. // try to strip quotes from the matchFor value. We want
  434. // [attrName=howdy] to match the same
  435. // as [attrName = 'howdy' ]
  436. if ((cmf.charAt(0) == '"') || (cmf.charAt(0) == "'")) {
  437. cp.matchFor = cmf.slice(1, -1);
  438. }
  439. }
  440. // end the attribute by adding it to the list of attributes.
  441. currentPart.attrs.push(cp);
  442. cp = null; // necessary?
  443. inBrackets = inMatchFor = -1;
  444. } else if (cc == '=') {
  445. // if the last char was an operator prefix, make sure we
  446. // record it along with the '=' operator.
  447. var addToCc = ('|~^$*'.indexOf(lc) >= 0) ? lc : '';
  448. cp.type = addToCc + cc;
  449. cp.attr = ts(inBrackets + 1, x - addToCc.length);
  450. inMatchFor = x + 1;
  451. }
  452. // now look for other clause parts
  453. } else if (inParens >= 0) {
  454. // if we're in a parenthetical expression, we need to figure
  455. // out if it's attached to a pseudo-selector rule like
  456. // :nth-child(1)
  457. if (cc == ')') {
  458. if (inPseudo >= 0) {
  459. cp.value = ts(inParens + 1, x);
  460. }
  461. inPseudo = inParens = -1;
  462. }
  463. } else if (cc == '#') {
  464. // start of an ID match
  465. endAll();
  466. inId = x + 1;
  467. } else if (cc == '.') {
  468. // start of a class match
  469. endAll();
  470. inClass = x;
  471. } else if (cc == ':') {
  472. // start of a pseudo-selector match
  473. endAll();
  474. inPseudo = x;
  475. } else if (cc == '[') {
  476. // start of an attribute match.
  477. endAll();
  478. inBrackets = x;
  479. // provide a new structure for the attribute match to fill-in
  480. cp = {
  481. /*=====
  482. attr: null, type: null, matchFor: null
  483. =====*/
  484. };
  485. } else if (cc == '(') {
  486. // we really only care if we've entered a parenthetical
  487. // expression if we're already inside a pseudo-selector match
  488. if (inPseudo >= 0) {
  489. // provide a new structure for the pseudo match to fill-in
  490. cp = {name: ts(inPseudo + 1, x), value: null};
  491. currentPart.pseudos.push(cp);
  492. }
  493. inParens = x;
  494. } else if (
  495. (cc == ' ') &&
  496. // if it's a space char and the last char is too, consume the
  497. // current one without doing more work
  498. (lc != cc)
  499. ) {
  500. endPart();
  501. }
  502. }
  503. return queryParts;
  504. };
  505. ////////////////////////////////////////////////////////////////////////
  506. // DOM query infrastructure
  507. ////////////////////////////////////////////////////////////////////////
  508. var agree = function(first, second) {
  509. // the basic building block of the yes/no chaining system. agree(f1,
  510. // f2) generates a new function which returns the boolean results of
  511. // both of the passed functions to a single logical-anded result. If
  512. // either are not passed, the other is used exclusively.
  513. if (!first) {
  514. return second;
  515. }
  516. if (!second) {
  517. return first;
  518. }
  519. return function() {
  520. return first.apply(window, arguments) && second.apply(window, arguments);
  521. }
  522. };
  523. /**
  524. * @param {(number|string|Node)} i
  525. * @param {Array=} opt_arr
  526. */
  527. function getArr(i, opt_arr) {
  528. // helps us avoid array alloc when we don't need it
  529. var r = opt_arr || [];
  530. if (i) {
  531. r.push(i);
  532. }
  533. return r;
  534. }
  535. var isElement = function(n) {
  536. return (1 == n.nodeType);
  537. };
  538. // FIXME: need to coalesce getAttr with defaultGetter
  539. var blank = '';
  540. var getAttr = function(elem, attr) {
  541. if (!elem) {
  542. return blank;
  543. }
  544. if (attr == 'class') {
  545. return elem.className || blank;
  546. }
  547. if (attr == 'for') {
  548. return elem.htmlFor || blank;
  549. }
  550. if (attr == 'style') {
  551. return elem.style.cssText || blank;
  552. }
  553. return (caseSensitive ? elem.getAttribute(attr) :
  554. elem.getAttribute(attr, 2)) || blank;
  555. };
  556. var attrs = {
  557. '*=': function(attr, value) {
  558. return function(elem) {
  559. // E[foo*="bar"]
  560. // an E element whose "foo" attribute value contains
  561. // the substring "bar"
  562. return (getAttr(elem, attr).indexOf(value) >= 0);
  563. }
  564. },
  565. '^=': function(attr, value) {
  566. // E[foo^="bar"]
  567. // an E element whose "foo" attribute value begins exactly
  568. // with the string "bar"
  569. return function(elem) {
  570. return (getAttr(elem, attr).indexOf(value) == 0);
  571. }
  572. },
  573. '$=': function(attr, value) {
  574. // E[foo$="bar"]
  575. // an E element whose "foo" attribute value ends exactly
  576. // with the string "bar"
  577. var tval = ' ' + value;
  578. return function(elem) {
  579. var ea = ' ' + getAttr(elem, attr);
  580. return (ea.lastIndexOf(tval) == (ea.length - tval.length));
  581. }
  582. },
  583. '~=': function(attr, value) {
  584. // E[foo~="bar"]
  585. // an E element whose "foo" attribute value is a list of
  586. // space-separated values, one of which is exactly equal
  587. // to "bar"
  588. var tval = ' ' + value + ' ';
  589. return function(elem) {
  590. var ea = ' ' + getAttr(elem, attr) + ' ';
  591. return (ea.indexOf(tval) >= 0);
  592. }
  593. },
  594. '|=': function(attr, value) {
  595. // E[hreflang|="en"]
  596. // an E element whose "hreflang" attribute has a
  597. // hyphen-separated list of values beginning (from the
  598. // left) with "en"
  599. value = ' ' + value;
  600. return function(elem) {
  601. var ea = ' ' + getAttr(elem, attr);
  602. return (
  603. (ea == value) ||
  604. (ea.indexOf(value + '-') == 0)
  605. );
  606. }
  607. },
  608. '=': function(attr, value) {
  609. return function(elem) {
  610. return (getAttr(elem, attr) == value);
  611. }
  612. }
  613. };
  614. // avoid testing for node type if we can. Defining this in the negative
  615. // here to avoid negation in the fast path.
  616. var noNextElementSibling = (
  617. typeof goog.dom.getDocument().firstChild.nextElementSibling == 'undefined'
  618. );
  619. var nSibling = !noNextElementSibling ? 'nextElementSibling' : 'nextSibling';
  620. var pSibling = !noNextElementSibling ?
  621. 'previousElementSibling' :
  622. 'previousSibling';
  623. var simpleNodeTest = (noNextElementSibling ? isElement : goog.functions.TRUE);
  624. var _lookLeft = function(node) {
  625. while (node = node[pSibling]) {
  626. if (simpleNodeTest(node)) {
  627. return false;
  628. }
  629. }
  630. return true;
  631. };
  632. var _lookRight = function(node) {
  633. while (node = node[nSibling]) {
  634. if (simpleNodeTest(node)) {
  635. return false;
  636. }
  637. }
  638. return true;
  639. };
  640. var getNodeIndex = function(node) {
  641. var root = node.parentNode;
  642. var i = 0,
  643. tret = root[childNodesName],
  644. ci = (node['_i'] || -1),
  645. cl = (root['_l'] || -1);
  646. if (!tret) {
  647. return -1;
  648. }
  649. var l = tret.length;
  650. // we calculate the parent length as a cheap way to invalidate the
  651. // cache. It's not 100% accurate, but it's much more honest than what
  652. // other libraries do
  653. if (cl == l && ci >= 0 && cl >= 0) {
  654. // if it's legit, tag and release
  655. return ci;
  656. }
  657. // else re-key things
  658. root['_l'] = l;
  659. ci = -1;
  660. var te = root['firstElementChild'] || root['firstChild'];
  661. for (; te; te = te[nSibling]) {
  662. if (simpleNodeTest(te)) {
  663. te['_i'] = ++i;
  664. if (node === te) {
  665. // NOTE:
  666. // shortcutting the return at this step in indexing works
  667. // very well for benchmarking but we avoid it here since
  668. // it leads to potential O(n^2) behavior in sequential
  669. // getNodexIndex operations on a previously un-indexed
  670. // parent. We may revisit this at a later time, but for
  671. // now we just want to get the right answer more often
  672. // than not.
  673. ci = i;
  674. }
  675. }
  676. }
  677. return ci;
  678. };
  679. var isEven = function(elem) {
  680. return !((getNodeIndex(elem)) % 2);
  681. };
  682. var isOdd = function(elem) {
  683. return (getNodeIndex(elem)) % 2;
  684. };
  685. var pseudos = {
  686. 'checked': function(name, condition) {
  687. return function(elem) {
  688. return elem.checked || elem.attributes['checked'];
  689. }
  690. },
  691. 'first-child': function() {
  692. return _lookLeft;
  693. },
  694. 'last-child': function() {
  695. return _lookRight;
  696. },
  697. 'only-child': function(name, condition) {
  698. return function(node) {
  699. if (!_lookLeft(node)) {
  700. return false;
  701. }
  702. if (!_lookRight(node)) {
  703. return false;
  704. }
  705. return true;
  706. };
  707. },
  708. 'empty': function(name, condition) {
  709. return function(elem) {
  710. // DomQuery and jQuery get this wrong, oddly enough.
  711. // The CSS 3 selectors spec is pretty explicit about it, too.
  712. var cn = elem.childNodes;
  713. var cnl = elem.childNodes.length;
  714. // if(!cnl) { return true; }
  715. for (var x = cnl - 1; x >= 0; x--) {
  716. var nt = cn[x].nodeType;
  717. if ((nt === 1) || (nt == 3)) {
  718. return false;
  719. }
  720. }
  721. return true;
  722. }
  723. },
  724. 'contains': function(name, condition) {
  725. var cz = condition.charAt(0);
  726. if (cz == '"' || cz == "'") { // Remove quotes.
  727. condition = condition.slice(1, -1);
  728. }
  729. return function(elem) {
  730. return (elem.innerHTML.indexOf(condition) >= 0);
  731. }
  732. },
  733. 'not': function(name, condition) {
  734. var p = getQueryParts(condition)[0];
  735. var ignores = { el: 1 };
  736. if (p.tag != '*') {
  737. ignores.tag = 1;
  738. }
  739. if (!p.classes.length) {
  740. ignores.classes = 1;
  741. }
  742. var ntf = getSimpleFilterFunc(p, ignores);
  743. return function(elem) {
  744. return !ntf(elem);
  745. }
  746. },
  747. 'nth-child': function(name, condition) {
  748. function pi(n) {
  749. return parseInt(n, 10);
  750. }
  751. // avoid re-defining function objects if we can
  752. if (condition == 'odd') {
  753. return isOdd;
  754. } else if (condition == 'even') {
  755. return isEven;
  756. }
  757. // FIXME: can we shorten this?
  758. if (condition.indexOf('n') != -1) {
  759. var tparts = condition.split('n', 2);
  760. var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1;
  761. var idx = tparts[1] ? pi(tparts[1]) : 0;
  762. var lb = 0, ub = -1;
  763. if (pred > 0) {
  764. if (idx < 0) {
  765. idx = (idx % pred) && (pred + (idx % pred));
  766. } else if (idx > 0) {
  767. if (idx >= pred) {
  768. lb = idx - idx % pred;
  769. }
  770. idx = idx % pred;
  771. }
  772. } else if (pred < 0) {
  773. pred *= -1;
  774. // idx has to be greater than 0 when pred is negative;
  775. // shall we throw an error here?
  776. if (idx > 0) {
  777. ub = idx;
  778. idx = idx % pred;
  779. }
  780. }
  781. if (pred > 0) {
  782. return function(elem) {
  783. var i = getNodeIndex(elem);
  784. return (i >= lb) && (ub < 0 || i <= ub) && ((i % pred) == idx);
  785. }
  786. } else {
  787. condition = idx;
  788. }
  789. }
  790. var ncount = pi(condition);
  791. return function(elem) {
  792. return (getNodeIndex(elem) == ncount);
  793. }
  794. }
  795. };
  796. var defaultGetter = (legacyIE) ? function(cond) {
  797. var clc = cond.toLowerCase();
  798. if (clc == 'class') {
  799. cond = 'className';
  800. }
  801. return function(elem) {
  802. return caseSensitive ? elem.getAttribute(cond) : elem[cond] || elem[clc];
  803. }
  804. } : function(cond) {
  805. return function(elem) {
  806. return elem && elem.getAttribute && elem.hasAttribute(cond);
  807. }
  808. };
  809. var getSimpleFilterFunc = function(query, ignores) {
  810. // Generates a node tester function based on the passed query part. The
  811. // query part is one of the structures generated by the query parser when it
  812. // creates the query AST. The 'ignores' object specifies which (if any)
  813. // tests to skip, allowing the system to avoid duplicating work where it
  814. // may have already been taken into account by other factors such as how
  815. // the nodes to test were fetched in the first place.
  816. if (!query) {
  817. return goog.functions.TRUE;
  818. }
  819. ignores = ignores || {};
  820. var ff = null;
  821. if (!ignores.el) {
  822. ff = agree(ff, isElement);
  823. }
  824. if (!ignores.tag) {
  825. if (query.tag != '*') {
  826. ff = agree(ff, function(elem) {
  827. return (elem && (elem.tagName == query.getTag()));
  828. });
  829. }
  830. }
  831. if (!ignores.classes) {
  832. goog.array.forEach(query.classes, function(cname, idx, arr) {
  833. // Get the class name.
  834. var re = new RegExp('(?:^|\\s)' + cname + '(?:\\s|$)');
  835. ff = agree(ff, function(elem) {
  836. return re.test(elem.className);
  837. });
  838. ff.count = idx;
  839. });
  840. }
  841. if (!ignores.pseudos) {
  842. goog.array.forEach(query.pseudos, function(pseudo) {
  843. var pn = pseudo.name;
  844. if (pseudos[pn]) {
  845. ff = agree(ff, pseudos[pn](pn, pseudo.value));
  846. }
  847. });
  848. }
  849. if (!ignores.attrs) {
  850. goog.array.forEach(query.attrs, function(attr) {
  851. var matcher;
  852. var a = attr.attr;
  853. // type, attr, matchFor
  854. if (attr.type && attrs[attr.type]) {
  855. matcher = attrs[attr.type](a, attr.matchFor);
  856. } else if (a.length) {
  857. matcher = defaultGetter(a);
  858. }
  859. if (matcher) {
  860. ff = agree(ff, matcher);
  861. }
  862. });
  863. }
  864. if (!ignores.id) {
  865. if (query.id) {
  866. ff = agree(ff, function(elem) {
  867. return (!!elem && (elem.id == query.id));
  868. });
  869. }
  870. }
  871. if (!ff) {
  872. if (!('default' in ignores)) {
  873. ff = goog.functions.TRUE;
  874. }
  875. }
  876. return ff;
  877. };
  878. var nextSiblingIterator = function(filterFunc) {
  879. return function(node, ret, bag) {
  880. while (node = node[nSibling]) {
  881. if (noNextElementSibling && (!isElement(node))) {
  882. continue;
  883. }
  884. if (
  885. (!bag || _isUnique(node, bag)) &&
  886. filterFunc(node)
  887. ) {
  888. ret.push(node);
  889. }
  890. break;
  891. }
  892. return ret;
  893. };
  894. };
  895. var nextSiblingsIterator = function(filterFunc) {
  896. return function(root, ret, bag) {
  897. var te = root[nSibling];
  898. while (te) {
  899. if (simpleNodeTest(te)) {
  900. if (bag && !_isUnique(te, bag)) {
  901. break;
  902. }
  903. if (filterFunc(te)) {
  904. ret.push(te);
  905. }
  906. }
  907. te = te[nSibling];
  908. }
  909. return ret;
  910. };
  911. };
  912. // Get an array of child *elements*, skipping text and comment nodes
  913. var _childElements = function(filterFunc) {
  914. filterFunc = filterFunc || goog.functions.TRUE;
  915. return function(root, ret, bag) {
  916. var te, x = 0, tret = root[childNodesName];
  917. while (te = tret[x++]) {
  918. if (
  919. simpleNodeTest(te) &&
  920. (!bag || _isUnique(te, bag)) &&
  921. (filterFunc(te, x))
  922. ) {
  923. ret.push(te);
  924. }
  925. }
  926. return ret;
  927. };
  928. };
  929. // test to see if node is below root
  930. var _isDescendant = function(node, root) {
  931. var pn = node.parentNode;
  932. while (pn) {
  933. if (pn == root) {
  934. break;
  935. }
  936. pn = pn.parentNode;
  937. }
  938. return !!pn;
  939. };
  940. var _getElementsFuncCache = {};
  941. var getElementsFunc = function(query) {
  942. var retFunc = _getElementsFuncCache[query.query];
  943. // If we've got a cached dispatcher, just use that.
  944. if (retFunc) {
  945. return retFunc;
  946. }
  947. // Else, generate a new one.
  948. // NOTE:
  949. // This function returns a function that searches for nodes and
  950. // filters them. The search may be specialized by infix operators
  951. // (">", "~", or "+") else it will default to searching all
  952. // descendants (the " " selector). Once a group of children is
  953. // found, a test function is applied to weed out the ones we
  954. // don't want. Many common cases can be fast-pathed. We spend a
  955. // lot of cycles to create a dispatcher that doesn't do more work
  956. // than necessary at any point since, unlike this function, the
  957. // dispatchers will be called every time. The logic of generating
  958. // efficient dispatchers looks like this in pseudo code:
  959. //
  960. // # if it's a purely descendant query (no ">", "+", or "~" modifiers)
  961. // if infixOperator == " ":
  962. // if only(id):
  963. // return def(root):
  964. // return d.byId(id, root);
  965. //
  966. // elif id:
  967. // return def(root):
  968. // return filter(d.byId(id, root));
  969. //
  970. // elif cssClass && getElementsByClassName:
  971. // return def(root):
  972. // return filter(root.getElementsByClassName(cssClass));
  973. //
  974. // elif only(tag):
  975. // return def(root):
  976. // return root.getElementsByTagName(tagName);
  977. //
  978. // else:
  979. // # search by tag name, then filter
  980. // return def(root):
  981. // return filter(root.getElementsByTagName(tagName||"*"));
  982. //
  983. // elif infixOperator == ">":
  984. // # search direct children
  985. // return def(root):
  986. // return filter(root.children);
  987. //
  988. // elif infixOperator == "+":
  989. // # search next sibling
  990. // return def(root):
  991. // return filter(root.nextElementSibling);
  992. //
  993. // elif infixOperator == "~":
  994. // # search rightward siblings
  995. // return def(root):
  996. // return filter(nextSiblings(root));
  997. var io = query.infixOper;
  998. var oper = (io ? io.oper : '');
  999. // The default filter func which tests for all conditions in the query
  1000. // part. This is potentially inefficient, so some optimized paths may
  1001. // re-define it to test fewer things.
  1002. var filterFunc = getSimpleFilterFunc(query, { el: 1 });
  1003. var qt = query.tag;
  1004. var wildcardTag = ('*' == qt);
  1005. var ecs = goog.dom.getDocument()['getElementsByClassName'];
  1006. if (!oper) {
  1007. // If there's no infix operator, then it's a descendant query. ID
  1008. // and "elements by class name" variants can be accelerated so we
  1009. // call them out explicitly:
  1010. if (query.id) {
  1011. // Testing shows that the overhead of goog.functions.TRUE() is
  1012. // acceptable and can save us some bytes vs. re-defining the function
  1013. // everywhere.
  1014. filterFunc = (!query.loops && wildcardTag) ?
  1015. goog.functions.TRUE :
  1016. getSimpleFilterFunc(query, { el: 1, id: 1 });
  1017. retFunc = function(root, arr) {
  1018. var te = goog.dom.getDomHelper(root).getElement(query.id);
  1019. if (!te || !filterFunc(te)) {
  1020. return;
  1021. }
  1022. if (9 == root.nodeType) { // If root's a doc, we just return directly.
  1023. return getArr(te, arr);
  1024. } else { // otherwise check ancestry
  1025. if (_isDescendant(te, root)) {
  1026. return getArr(te, arr);
  1027. }
  1028. }
  1029. };
  1030. } else if (
  1031. ecs &&
  1032. // isAlien check. Workaround for Prototype.js being totally evil/dumb.
  1033. /\{\s*\[native code\]\s*\}/.test(String(ecs)) &&
  1034. query.classes.length &&
  1035. // WebKit bug where quirks-mode docs select by class w/o case
  1036. // sensitivity.
  1037. !cssCaseBug
  1038. ) {
  1039. // it's a class-based query and we've got a fast way to run it.
  1040. // ignore class and ID filters since we will have handled both
  1041. filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 });
  1042. var classesString = query.classes.join(' ');
  1043. retFunc = function(root, arr) {
  1044. var ret = getArr(0, arr), te, x = 0;
  1045. var tret = root.getElementsByClassName(classesString);
  1046. while ((te = tret[x++])) {
  1047. if (filterFunc(te, root)) {
  1048. ret.push(te);
  1049. }
  1050. }
  1051. return ret;
  1052. };
  1053. } else if (!wildcardTag && !query.loops) {
  1054. // it's tag only. Fast-path it.
  1055. retFunc = function(root, arr) {
  1056. var ret = getArr(0, arr), te, x = 0;
  1057. var tret = root.getElementsByTagName(query.getTag());
  1058. while ((te = tret[x++])) {
  1059. ret.push(te);
  1060. }
  1061. return ret;
  1062. };
  1063. } else {
  1064. // the common case:
  1065. // a descendant selector without a fast path. By now it's got
  1066. // to have a tag selector, even if it's just "*" so we query
  1067. // by that and filter
  1068. filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 });
  1069. retFunc = function(root, arr) {
  1070. var ret = getArr(0, arr), te, x = 0;
  1071. // we use getTag() to avoid case sensitivity issues
  1072. var tret = root.getElementsByTagName(query.getTag());
  1073. while (te = tret[x++]) {
  1074. if (filterFunc(te, root)) {
  1075. ret.push(te);
  1076. }
  1077. }
  1078. return ret;
  1079. };
  1080. }
  1081. } else {
  1082. // the query is scoped in some way. Instead of querying by tag we
  1083. // use some other collection to find candidate nodes
  1084. var skipFilters = { el: 1 };
  1085. if (wildcardTag) {
  1086. skipFilters.tag = 1;
  1087. }
  1088. filterFunc = getSimpleFilterFunc(query, skipFilters);
  1089. if ('+' == oper) {
  1090. retFunc = nextSiblingIterator(filterFunc);
  1091. } else if ('~' == oper) {
  1092. retFunc = nextSiblingsIterator(filterFunc);
  1093. } else if ('>' == oper) {
  1094. retFunc = _childElements(filterFunc);
  1095. }
  1096. }
  1097. // cache it and return
  1098. return _getElementsFuncCache[query.query] = retFunc;
  1099. };
  1100. var filterDown = function(root, queryParts) {
  1101. // NOTE:
  1102. // this is the guts of the DOM query system. It takes a list of
  1103. // parsed query parts and a root and finds children which match
  1104. // the selector represented by the parts
  1105. var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret;
  1106. for (var i = 0; i < qpl; i++) {
  1107. ret = [];
  1108. qp = queryParts[i];
  1109. x = candidates.length - 1;
  1110. if (x > 0) {
  1111. // if we have more than one root at this level, provide a new
  1112. // hash to use for checking group membership but tell the
  1113. // system not to post-filter us since we will already have been
  1114. // guaranteed to be unique
  1115. bag = {};
  1116. ret.nozip = true;
  1117. }
  1118. var gef = getElementsFunc(qp);
  1119. for (var j = 0; te = candidates[j]; j++) {
  1120. // for every root, get the elements that match the descendant
  1121. // selector, adding them to the 'ret' array and filtering them
  1122. // via membership in this level's bag. If there are more query
  1123. // parts, then this level's return will be used as the next
  1124. // level's candidates
  1125. gef(te, ret, bag);
  1126. }
  1127. if (!ret.length) { break; }
  1128. candidates = ret;
  1129. }
  1130. return ret;
  1131. };
  1132. ////////////////////////////////////////////////////////////////////////
  1133. // the query runner
  1134. ////////////////////////////////////////////////////////////////////////
  1135. // these are the primary caches for full-query results. The query
  1136. // dispatcher functions are generated then stored here for hash lookup in
  1137. // the future
  1138. var _queryFuncCacheDOM = {},
  1139. _queryFuncCacheQSA = {};
  1140. // this is the second level of splitting, from full-length queries (e.g.,
  1141. // 'div.foo .bar') into simple query expressions (e.g., ['div.foo',
  1142. // '.bar'])
  1143. var getStepQueryFunc = function(query) {
  1144. var qparts = getQueryParts(goog.string.trim(query));
  1145. // if it's trivial, avoid iteration and zipping costs
  1146. if (qparts.length == 1) {
  1147. // We optimize this case here to prevent dispatch further down the
  1148. // chain, potentially slowing things down. We could more elegantly
  1149. // handle this in filterDown(), but it's slower for simple things
  1150. // that need to be fast (e.g., '#someId').
  1151. var tef = getElementsFunc(qparts[0]);
  1152. return function(root) {
  1153. var r = tef(root, []);
  1154. if (r) { r.nozip = true; }
  1155. return r;
  1156. }
  1157. }
  1158. // otherwise, break it up and return a runner that iterates over the parts
  1159. // recursively
  1160. return function(root) {
  1161. return filterDown(root, qparts);
  1162. }
  1163. };
  1164. // NOTES:
  1165. // * we can't trust QSA for anything but document-rooted queries, so
  1166. // caching is split into DOM query evaluators and QSA query evaluators
  1167. // * caching query results is dirty and leak-prone (or, at a minimum,
  1168. // prone to unbounded growth). Other toolkits may go this route, but
  1169. // they totally destroy their own ability to manage their memory
  1170. // footprint. If we implement it, it should only ever be with a fixed
  1171. // total element reference # limit and an LRU-style algorithm since JS
  1172. // has no weakref support. Caching compiled query evaluators is also
  1173. // potentially problematic, but even on large documents the size of the
  1174. // query evaluators is often < 100 function objects per evaluator (and
  1175. // LRU can be applied if it's ever shown to be an issue).
  1176. // * since IE's QSA support is currently only for HTML documents and even
  1177. // then only in IE 8's 'standards mode', we have to detect our dispatch
  1178. // route at query time and keep 2 separate caches. Ugg.
  1179. var qsa = 'querySelectorAll';
  1180. // some versions of Safari provided QSA, but it was buggy and crash-prone.
  1181. // We need to detect the right 'internal' webkit version to make this work.
  1182. var qsaAvail = (
  1183. !!goog.dom.getDocument()[qsa] &&
  1184. // see #5832
  1185. (!goog.userAgent.WEBKIT || goog.userAgent.isVersionOrHigher('526'))
  1186. );
  1187. /**
  1188. * @param {(string|Array)} query
  1189. * @param {boolean=} opt_forceDOM
  1190. * @return {function((string|Node)): !Array}
  1191. */
  1192. var getQueryFunc = function(query, opt_forceDOM) {
  1193. if (qsaAvail) {
  1194. // if we've got a cached variant and we think we can do it, run it!
  1195. var qsaCached = _queryFuncCacheQSA[query];
  1196. if (qsaCached && !opt_forceDOM) {
  1197. return qsaCached;
  1198. }
  1199. }
  1200. // else if we've got a DOM cached variant, assume that we already know
  1201. // all we need to and use it
  1202. var domCached = _queryFuncCacheDOM[query];
  1203. if (domCached) {
  1204. return domCached;
  1205. }
  1206. // TODO:
  1207. // today we're caching DOM and QSA branches separately so we
  1208. // recalc useQSA every time. If we had a way to tag root+query
  1209. // efficiently, we'd be in good shape to do a global cache.
  1210. var qcz = query.charAt(0);
  1211. var nospace = (-1 == query.indexOf(' '));
  1212. // byId searches are wicked fast compared to QSA, even when filtering
  1213. // is required
  1214. if ((query.indexOf('#') >= 0) && (nospace)) {
  1215. opt_forceDOM = true;
  1216. }
  1217. var useQSA = (
  1218. qsaAvail && (!opt_forceDOM) &&
  1219. // as per CSS 3, we can't currently start w/ combinator:
  1220. // http://www.w3.org/TR/css3-selectors/#w3cselgrammar
  1221. (specials.indexOf(qcz) == -1) &&
  1222. // IE's QSA impl sucks on pseudos
  1223. (!legacyIE || (query.indexOf(':') == -1)) &&
  1224. (!(cssCaseBug && (query.indexOf('.') >= 0))) &&
  1225. // FIXME:
  1226. // need to tighten up browser rules on ':contains' and '|=' to
  1227. // figure out which aren't good
  1228. (query.indexOf(':contains') == -1) &&
  1229. (query.indexOf('|=') == -1) // some browsers don't understand it
  1230. );
  1231. // TODO:
  1232. // if we've got a descendant query (e.g., '> .thinger' instead of
  1233. // just '.thinger') in a QSA-able doc, but are passed a child as a
  1234. // root, it should be possible to give the item a synthetic ID and
  1235. // trivially rewrite the query to the form '#synid > .thinger' to
  1236. // use the QSA branch
  1237. if (useQSA) {
  1238. var tq = (specials.indexOf(query.charAt(query.length - 1)) >= 0) ?
  1239. (query + ' *') : query;
  1240. return _queryFuncCacheQSA[query] = function(root) {
  1241. try {
  1242. // the QSA system contains an egregious spec bug which
  1243. // limits us, effectively, to only running QSA queries over
  1244. // entire documents. See:
  1245. // http://ejohn.org/blog/thoughts-on-queryselectorall/
  1246. // despite this, we can also handle QSA runs on simple
  1247. // selectors, but we don't want detection to be expensive
  1248. // so we're just checking for the presence of a space char
  1249. // right now. Not elegant, but it's cheaper than running
  1250. // the query parser when we might not need to
  1251. if (!((9 == root.nodeType) || nospace)) {
  1252. throw new Error('');
  1253. }
  1254. var r = root[qsa](tq);
  1255. // IE QSA queries may incorrectly include comment nodes, so we throw
  1256. // the zipping function into 'remove' comments mode instead of the
  1257. // normal 'skip it' which every other QSA-clued browser enjoys
  1258. // skip expensive duplication checks and just wrap in an array.
  1259. if (legacyIE) {
  1260. r.commentStrip = true;
  1261. } else {
  1262. r.nozip = true;
  1263. }
  1264. return r;
  1265. } catch (e) {
  1266. // else run the DOM branch on this query, ensuring that we
  1267. // default that way in the future
  1268. return getQueryFunc(query, true)(root);
  1269. }
  1270. };
  1271. } else {
  1272. // DOM branch
  1273. var parts = query.split(/\s*,\s*/);
  1274. return _queryFuncCacheDOM[query] =
  1275. ((parts.length < 2) ?
  1276. // if not a compound query (e.g., '.foo, .bar'), cache and
  1277. // return a dispatcher
  1278. getStepQueryFunc(query) :
  1279. // if it *is* a complex query, break it up into its
  1280. // constituent parts and return a dispatcher that will
  1281. // merge the parts when run
  1282. function(root) {
  1283. var pindex =
  1284. 0, // avoid array alloc for every invocation
  1285. ret = [],
  1286. tp;
  1287. while (tp = parts[pindex++]) {
  1288. ret = ret.concat(getStepQueryFunc(tp)(root));
  1289. }
  1290. return ret;
  1291. });
  1292. }
  1293. };
  1294. var _zipIdx = 0;
  1295. // NOTE:
  1296. // this function is Moo inspired, but our own impl to deal correctly
  1297. // with XML in IE
  1298. var _nodeUID = legacyIE ? function(node) {
  1299. if (caseSensitive) {
  1300. // XML docs don't have uniqueID on their nodes
  1301. return node.getAttribute('_uid') ||
  1302. node.setAttribute('_uid', ++_zipIdx) || _zipIdx;
  1303. } else {
  1304. return node.uniqueID;
  1305. }
  1306. } :
  1307. function(node) {
  1308. return (node['_uid'] || (node['_uid'] = ++_zipIdx));
  1309. };
  1310. // determine if a node in is unique in a 'bag'. In this case we don't want
  1311. // to flatten a list of unique items, but rather just tell if the item in
  1312. // question is already in the bag. Normally we'd just use hash lookup to do
  1313. // this for us but IE's DOM is busted so we can't really count on that. On
  1314. // the upside, it gives us a built in unique ID function.
  1315. var _isUnique = function(node, bag) {
  1316. if (!bag) {
  1317. return 1;
  1318. }
  1319. var id = _nodeUID(node);
  1320. if (!bag[id]) {
  1321. return bag[id] = 1;
  1322. }
  1323. return 0;
  1324. };
  1325. // attempt to efficiently determine if an item in a list is a dupe,
  1326. // returning a list of 'uniques', hopefully in document order
  1327. var _zipIdxName = '_zipIdx';
  1328. var _zip = function(arr) {
  1329. if (arr && arr.nozip) {
  1330. return arr;
  1331. }
  1332. var ret = [];
  1333. if (!arr || !arr.length) {
  1334. return ret;
  1335. }
  1336. if (arr[0]) {
  1337. ret.push(arr[0]);
  1338. }
  1339. if (arr.length < 2) {
  1340. return ret;
  1341. }
  1342. _zipIdx++;
  1343. // we have to fork here for IE and XML docs because we can't set
  1344. // expandos on their nodes (apparently). *sigh*
  1345. if (legacyIE && caseSensitive) {
  1346. var szidx = _zipIdx + '';
  1347. arr[0].setAttribute(_zipIdxName, szidx);
  1348. for (var x = 1, te; te = arr[x]; x++) {
  1349. if (arr[x].getAttribute(_zipIdxName) != szidx) {
  1350. ret.push(te);
  1351. }
  1352. te.setAttribute(_zipIdxName, szidx);
  1353. }
  1354. } else if (legacyIE && arr.commentStrip) {
  1355. try {
  1356. for (var x = 1, te; te = arr[x]; x++) {
  1357. if (isElement(te)) {
  1358. ret.push(te);
  1359. }
  1360. }
  1361. } catch (e) { /* squelch */ }
  1362. } else {
  1363. if (arr[0]) {
  1364. arr[0][_zipIdxName] = _zipIdx;
  1365. }
  1366. for (var x = 1, te; te = arr[x]; x++) {
  1367. if (arr[x][_zipIdxName] != _zipIdx) {
  1368. ret.push(te);
  1369. }
  1370. te[_zipIdxName] = _zipIdx;
  1371. }
  1372. }
  1373. return ret;
  1374. };
  1375. /**
  1376. * The main executor. Type specification from above.
  1377. * @param {string|Array} query The query.
  1378. * @param {(string|Node)=} opt_root The root.
  1379. * @return {!Array} The elements that matched the query.
  1380. */
  1381. var query = function(query, opt_root) {
  1382. // NOTE: elementsById is not currently supported
  1383. // NOTE: ignores xpath-ish queries for now
  1384. //Set list constructor to desired value. This can change
  1385. //between calls, so always re-assign here.
  1386. if (!query) {
  1387. return [];
  1388. }
  1389. if (query.constructor == Array) {
  1390. return /** @type {!Array} */ (query);
  1391. }
  1392. if (!goog.isString(query)) {
  1393. return [query];
  1394. }
  1395. var root = opt_root;
  1396. if (goog.isString(root)) {
  1397. root = goog.dom.getElement(root);
  1398. if (!root) {
  1399. return [];
  1400. }
  1401. }
  1402. root = root || goog.dom.getDocument();
  1403. var od = root.ownerDocument || root.documentElement;
  1404. // throw the big case sensitivity switch
  1405. // NOTE:
  1406. // Opera in XHTML mode doesn't detect case-sensitivity correctly
  1407. // and it's not clear that there's any way to test for it
  1408. caseSensitive =
  1409. root.contentType && root.contentType == 'application/xml' ||
  1410. goog.userAgent.OPERA &&
  1411. (root.doctype || od.toString() == '[object XMLDocument]') ||
  1412. !!od &&
  1413. (legacyIE ? od.xml : (root.xmlVersion || od.xmlVersion));
  1414. // NOTE:
  1415. // adding 'true' as the 2nd argument to getQueryFunc is useful for
  1416. // testing the DOM branch without worrying about the
  1417. // behavior/performance of the QSA branch.
  1418. var r = getQueryFunc(query)(root);
  1419. // FIXME(slightlyoff):
  1420. // need to investigate this branch WRT dojo:#8074 and dojo:#8075
  1421. if (r && r.nozip) {
  1422. return r;
  1423. }
  1424. return _zip(r);
  1425. };
  1426. // FIXME: need to add infrastructure for post-filtering pseudos, ala :last
  1427. query.pseudos = pseudos;
  1428. return query;
  1429. })();
  1430. // TODO(arv): Please don't export here since it clobbers dead code elimination.
  1431. goog.exportSymbol('goog.dom.query', goog.dom.query);
  1432. goog.exportSymbol('goog.dom.query.pseudos', goog.dom.query.pseudos);