dom/tests/mochitest/ajax/mochikit/MochiKit/Selector.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /***
     3 MochiKit.Selector 1.4
     5 See <http://mochikit.com/> for documentation, downloads, license, etc.
     7 (c) 2005 Bob Ippolito and others.  All rights Reserved.
     9 ***/
    11 if (typeof(dojo) != 'undefined') {
    12     dojo.provide('MochiKit.Selector');
    13     dojo.require('MochiKit.Base');
    14     dojo.require('MochiKit.DOM');
    15     dojo.require('MochiKit.Iter');
    16 }
    18 if (typeof(JSAN) != 'undefined') {
    19     JSAN.use("MochiKit.Base", []);
    20     JSAN.use("MochiKit.DOM", []);
    21     JSAN.use("MochiKit.Iter", []);
    22 }
    24 try {
    25     if (typeof(MochiKit.Base) === 'undefined' ||
    26         typeof(MochiKit.DOM) === 'undefined' ||
    27         typeof(MochiKit.Iter) === 'undefined') {
    28         throw "";
    29     }
    30 } catch (e) {
    31     throw "MochiKit.Selector depends on MochiKit.Base, MochiKit.DOM and MochiKit.Iter!";
    32 }
    34 if (typeof(MochiKit.Selector) == 'undefined') {
    35     MochiKit.Selector = {};
    36 }
    38 MochiKit.Selector.NAME = "MochiKit.Selector";
    40 MochiKit.Selector.VERSION = "1.4";
    42 MochiKit.Selector.__repr__ = function () {
    43     return "[" + this.NAME + " " + this.VERSION + "]";
    44 };
    46 MochiKit.Selector.toString = function () {
    47     return this.__repr__();
    48 };
    50 MochiKit.Selector.EXPORT = [
    51     "Selector",
    52     "findChildElements",
    53     "findDocElements",
    54     "$$"
    55 ];
    57 MochiKit.Selector.EXPORT_OK = [
    58 ];
    60 MochiKit.Selector.Selector = function (expression) {
    61     this.params = {classNames: [], pseudoClassNames: []};
    62     this.expression = expression.toString().replace(/(^\s+|\s+$)/g, '');
    63     this.parseExpression();
    64     this.compileMatcher();
    65 };
    67 MochiKit.Selector.Selector.prototype = {
    68     /***
    70     Selector class: convenient object to make CSS selections.
    72     ***/
    73     __class__: MochiKit.Selector.Selector,
    75     /** @id MochiKit.Selector.Selector.prototype.parseExpression */
    76     parseExpression: function () {
    77         function abort(message) {
    78             throw 'Parse error in selector: ' + message;
    79         }
    81         if (this.expression == '')  {
    82             abort('empty expression');
    83         }
    85         var repr = MochiKit.Base.repr;
    86         var params = this.params;
    87         var expr = this.expression;
    88         var match, modifier, clause, rest;
    89         while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!^$*]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
    90             params.attributes = params.attributes || [];
    91             params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
    92             expr = match[1];
    93         }
    95         if (expr == '*') {
    96             return this.params.wildcard = true;
    97         }
    99         while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+(?:\([^)]*\))?)(.*)/i)) {
   100             modifier = match[1];
   101             clause = match[2];
   102             rest = match[3];
   103             switch (modifier) {
   104                 case '#':
   105                     params.id = clause;
   106                     break;
   107                 case '.':
   108                     params.classNames.push(clause);
   109                     break;
   110                 case ':':
   111                     params.pseudoClassNames.push(clause);
   112                     break;
   113                 case '':
   114                 case undefined:
   115                     params.tagName = clause.toUpperCase();
   116                     break;
   117                 default:
   118                     abort(repr(expr));
   119             }
   120             expr = rest;
   121         }
   123         if (expr.length > 0) {
   124             abort(repr(expr));
   125         }
   126     },
   128     /** @id MochiKit.Selector.Selector.prototype.buildMatchExpression */
   129     buildMatchExpression: function () {
   130         var repr = MochiKit.Base.repr;
   131         var params = this.params;
   132         var conditions = [];
   133         var clause, i;
   135         function childElements(element) {
   136             return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)";
   137         }
   139         if (params.wildcard) {
   140             conditions.push('true');
   141         }
   142         if (clause = params.id) {
   143             conditions.push('element.id == ' + repr(clause));
   144         }
   145         if (clause = params.tagName) {
   146             conditions.push('element.tagName.toUpperCase() == ' + repr(clause));
   147         }
   148         if ((clause = params.classNames).length > 0) {
   149             for (i = 0; i < clause.length; i++) {
   150                 conditions.push('MochiKit.DOM.hasElementClass(element, ' + repr(clause[i]) + ')');
   151             }
   152         }
   153         if ((clause = params.pseudoClassNames).length > 0) {
   154             for (i = 0; i < clause.length; i++) {
   155                 var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/);
   156                 var pseudoClass = match[1];
   157                 var pseudoClassArgument = match[2];
   158                 switch (pseudoClass) {
   159                     case 'root':
   160                         conditions.push('element.nodeType == 9 || element === element.ownerDocument.documentElement'); break;
   161                     case 'nth-child':
   162                     case 'nth-last-child':
   163                     case 'nth-of-type':
   164                     case 'nth-last-of-type':
   165                         match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/);
   166                         if (!match) {
   167                             throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument;
   168                         }
   169                         var a, b;
   170                         if (match[0] == 'odd') {
   171                             a = 2;
   172                             b = 1;
   173                         } else if (match[0] == 'even') {
   174                             a = 2;
   175                             b = 0;
   176                         } else {
   177                             a = match[2] && parseInt(match) || null;
   178                             b = parseInt(match[3]);
   179                         }
   180                         conditions.push('this.nthChild(element,' + a + ',' + b
   181                                         + ',' + !!pseudoClass.match('^nth-last')    // Reverse
   182                                         + ',' + !!pseudoClass.match('of-type$')     // Restrict to same tagName
   183                                         + ')');
   184                         break;
   185                     case 'first-child':
   186                         conditions.push('this.nthChild(element, null, 1)');
   187                         break;
   188                     case 'last-child':
   189                         conditions.push('this.nthChild(element, null, 1, true)');
   190                         break;
   191                     case 'first-of-type':
   192                         conditions.push('this.nthChild(element, null, 1, false, true)');
   193                         break;
   194                     case 'last-of-type':
   195                         conditions.push('this.nthChild(element, null, 1, true, true)');
   196                         break;
   197                     case 'only-child':
   198                         conditions.push(childElements('element.parentNode') + '.length == 1');
   199                         break;
   200                     case 'only-of-type':
   201                         conditions.push('MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, ' + childElements('element.parentNode') + ').length == 1');
   202                         break;
   203                     case 'empty':
   204                         conditions.push('element.childNodes.length == 0');
   205                         break;
   206                     case 'enabled':
   207                         conditions.push('(this.isUIElement(element) && element.disabled === false)');
   208                         break;
   209                     case 'disabled':
   210                         conditions.push('(this.isUIElement(element) && element.disabled === true)');
   211                         break;
   212                     case 'checked':
   213                         conditions.push('(this.isUIElement(element) && element.checked === true)');
   214                         break;
   215                     case 'not':
   216                         var subselector = new MochiKit.Selector.Selector(pseudoClassArgument);
   217                         conditions.push('!( ' + subselector.buildMatchExpression() + ')')
   218                         break;
   219                 }
   220             }
   221         }
   222         if (clause = params.attributes) {
   223             MochiKit.Base.map(function (attribute) {
   224                 var value = 'MochiKit.DOM.getNodeAttribute(element, ' + repr(attribute.name) + ')';
   225                 var splitValueBy = function (delimiter) {
   226                     return value + '.split(' + repr(delimiter) + ')';
   227                 }
   229                 switch (attribute.operator) {
   230                     case '=':
   231                         conditions.push(value + ' == ' + repr(attribute.value));
   232                         break;
   233                     case '~=':
   234                         conditions.push(value + ' && MochiKit.Base.findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1');
   235                         break;
   236                     case '^=':
   237                         conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value));
   238                         break;
   239                     case '$=':
   240                         conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value));
   241                         break;
   242                     case '*=':
   243                         conditions.push(value + '.match(' + repr(attribute.value) + ')');
   244                         break;
   245                     case '|=':
   246                         conditions.push(
   247                             value + ' && ' + splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase())
   248                         );
   249                         break;
   250                     case '!=':
   251                         conditions.push(value + ' != ' + repr(attribute.value));
   252                         break;
   253                     case '':
   254                     case undefined:
   255                         conditions.push(value + ' != null');
   256                         break;
   257                     default:
   258                         throw 'Unknown operator ' + attribute.operator + ' in selector';
   259                 }
   260             }, clause);
   261         }
   263         return conditions.join(' && ');
   264     },
   266     /** @id MochiKit.Selector.Selector.prototype.compileMatcher */
   267     compileMatcher: function () {
   268         this.match = new Function('element', 'if (!element.tagName) return false; \
   269                 return ' + this.buildMatchExpression());
   270     },
   272     /** @id MochiKit.Selector.Selector.prototype.nthChild */
   273     nthChild: function (element, a, b, reverse, sametag){
   274         var siblings = MochiKit.Base.filter(function (node) {
   275             return node.nodeType == 1;
   276         }, element.parentNode.childNodes);
   277         if (sametag) {
   278             siblings = MochiKit.Base.filter(function (node) {
   279                 return node.tagName == element.tagName;
   280             }, siblings);
   281         }
   282         if (reverse) {
   283             siblings = MochiKit.Iter.reversed(siblings);
   284         }
   285         if (a) {
   286             var actualIndex = MochiKit.Base.findIdentical(siblings, element);
   287             return ((actualIndex + 1 - b) / a) % 1 == 0;
   288         } else {
   289             return b == MochiKit.Base.findIdentical(siblings, element) + 1;
   290         }
   291     },
   293     /** @id MochiKit.Selector.Selector.prototype.isUIElement */
   294     isUIElement: function (element) {
   295         return MochiKit.Base.findValue(['input', 'button', 'select', 'option', 'textarea', 'object'],
   296                 element.tagName.toLowerCase()) > -1;
   297     },
   299     /** @id MochiKit.Selector.Selector.prototype.findElements */
   300     findElements: function (scope, axis) {
   301         var element;
   303         if (axis == undefined) {
   304             axis = "";
   305         }
   307         function inScope(element, scope) {
   308             if (axis == "") {
   309                 return MochiKit.DOM.isChildNode(element, scope);
   310             } else if (axis == ">") {
   311                 return element.parentNode == scope;
   312             } else if (axis == "+") {
   313                 return element == nextSiblingElement(scope);
   314             } else if (axis == "~") {
   315                 var sibling = scope;
   316                 while (sibling = nextSiblingElement(sibling)) {
   317                     if (element == sibling) {
   318                         return true;
   319                     }
   320                 }
   321                 return false;
   322             } else {
   323                 throw "Invalid axis: " + axis;
   324             }
   325         }
   327         if (element = MochiKit.DOM.getElement(this.params.id)) {
   328             if (this.match(element)) {
   329                 if (!scope || inScope(element, scope)) {
   330                     return [element];
   331                 }
   332             }
   333         }
   335         function nextSiblingElement(node) {
   336             node = node.nextSibling;
   337             while (node && node.nodeType != 1) {
   338                 node = node.nextSibling;
   339             }
   340             return node;
   341         }
   343         if (axis == "") {
   344             scope = (scope || MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName || '*');
   345         } else if (axis == ">") {
   346             if (!scope) {
   347                 throw "> combinator not allowed without preceeding expression";
   348             }
   349             scope = MochiKit.Base.filter(function (node) {
   350                 return node.nodeType == 1;
   351             }, scope.childNodes);
   352         } else if (axis == "+") {
   353             if (!scope) {
   354                 throw "+ combinator not allowed without preceeding expression";
   355             }
   356             scope = nextSiblingElement(scope) && [nextSiblingElement(scope)];
   357         } else if (axis == "~") {
   358             if (!scope) {
   359                 throw "~ combinator not allowed without preceeding expression";
   360             }
   361             var newscope = [];
   362             while (nextSiblingElement(scope)) {
   363                 scope = nextSiblingElement(scope);
   364                 newscope.push(scope);
   365             }
   366             scope = newscope;
   367         }
   369         if (!scope) {
   370             return [];
   371         }
   373         var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) {
   374             return this.match(scopeElt);
   375         }, this), scope);
   377         return results;
   378     },
   380     /** @id MochiKit.Selector.Selector.prototype.repr */
   381     repr: function () {
   382         return 'Selector(' + this.expression + ')';
   383     },
   385     toString: MochiKit.Base.forwardCall("repr")
   386 };
   388 MochiKit.Base.update(MochiKit.Selector, {
   390     /** @id MochiKit.Selector.findChildElements */
   391     findChildElements: function (element, expressions) {
   392         return MochiKit.Base.flattenArray(MochiKit.Base.map(function (expression) {
   393             var nextScope = "";
   394             return MochiKit.Iter.reduce(function (results, expr) {
   395                 if (match = expr.match(/^[>+~]$/)) {
   396                     nextScope = match[0];
   397                     return results;
   398                 } else {
   399                     var selector = new MochiKit.Selector.Selector(expr);
   400                     var elements = MochiKit.Iter.reduce(function (elements, result) {
   401                         return MochiKit.Base.extend(elements, selector.findElements(result || element, nextScope));
   402                     }, results, []);
   403                     nextScope = "";
   404                     return elements;
   405                 }
   406             }, expression.replace(/(^\s+|\s+$)/g, '').split(/\s+/), [null]);
   407         }, expressions));
   408     },
   410     findDocElements: function () {
   411         return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments);
   412     },
   414     __new__: function () {
   415         var m = MochiKit.Base;
   417         this.$$ = this.findDocElements;
   419         this.EXPORT_TAGS = {
   420             ":common": this.EXPORT,
   421             ":all": m.concat(this.EXPORT, this.EXPORT_OK)
   422         };
   424         m.nameFunctions(this);
   425     }
   426 });
   428 MochiKit.Selector.__new__();
   430 MochiKit.Base._exportSymbols(this, MochiKit.Selector);

mercurial