tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Selector.js (15739B)


      1 /***
      2 
      3 MochiKit.Selector 1.4
      4 
      5 See <http://mochikit.com/> for documentation, downloads, license, etc.
      6 
      7 (c) 2005 Bob Ippolito and others.  All rights Reserved.
      8 
      9 ***/
     10 
     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 }
     17 
     18 if (typeof(JSAN) != 'undefined') {
     19    JSAN.use("MochiKit.Base", []);
     20    JSAN.use("MochiKit.DOM", []);
     21    JSAN.use("MochiKit.Iter", []);
     22 }
     23 
     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 }
     33 
     34 if (typeof(MochiKit.Selector) == 'undefined') {
     35    MochiKit.Selector = {};
     36 }
     37 
     38 MochiKit.Selector.NAME = "MochiKit.Selector";
     39 
     40 MochiKit.Selector.VERSION = "1.4";
     41 
     42 MochiKit.Selector.__repr__ = function () {
     43    return "[" + this.NAME + " " + this.VERSION + "]";
     44 };
     45 
     46 MochiKit.Selector.toString = function () {
     47    return this.__repr__();
     48 };
     49 
     50 MochiKit.Selector.EXPORT = [
     51    "Selector",
     52    "findChildElements",
     53    "findDocElements",
     54    "$$"
     55 ];
     56 
     57 MochiKit.Selector.EXPORT_OK = [
     58 ];
     59 
     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 };
     66 
     67 MochiKit.Selector.Selector.prototype = {
     68    /***
     69 
     70    Selector class: convenient object to make CSS selections.
     71 
     72    ***/
     73    __class__: MochiKit.Selector.Selector,
     74 
     75    /** @id MochiKit.Selector.Selector.prototype.parseExpression */
     76    parseExpression: function () {
     77        function abort(message) {
     78            throw 'Parse error in selector: ' + message;
     79        }
     80 
     81        if (this.expression == '')  {
     82            abort('empty expression');
     83        }
     84 
     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        }
     94 
     95        if (expr == '*') {
     96            return this.params.wildcard = true;
     97        }
     98 
     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        }
    122 
    123        if (expr.length > 0) {
    124            abort(repr(expr));
    125        }
    126    },
    127 
    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;
    134 
    135        function childElements(element) {
    136            return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)";
    137        }
    138 
    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                }
    228 
    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        }
    262 
    263        return conditions.join(' && ');
    264    },
    265 
    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    },
    271 
    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    },
    292 
    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    },
    298 
    299    /** @id MochiKit.Selector.Selector.prototype.findElements */
    300    findElements: function (scope, axis) {
    301        var element;
    302 
    303        if (axis == undefined) {
    304            axis = "";
    305        }
    306 
    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        }
    326 
    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        }
    334 
    335        function nextSiblingElement(node) {
    336            node = node.nextSibling;
    337            while (node && node.nodeType != 1) {
    338                node = node.nextSibling;
    339            }
    340            return node;
    341        }
    342 
    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        }
    368 
    369        if (!scope) {
    370            return [];
    371        }
    372 
    373        var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) {
    374            return this.match(scopeElt);
    375        }, this), scope);
    376 
    377        return results;
    378    },
    379 
    380    /** @id MochiKit.Selector.Selector.prototype.repr */
    381    repr: function () {
    382        return 'Selector(' + this.expression + ')';
    383    },
    384 
    385    toString: MochiKit.Base.forwardCall("repr")
    386 };
    387 
    388 MochiKit.Base.update(MochiKit.Selector, {
    389 
    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    },
    409 
    410    findDocElements: function () {
    411        return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments);
    412    },
    413 
    414    __new__: function () {
    415        var m = MochiKit.Base;
    416 
    417        this.$$ = this.findDocElements;
    418 
    419        this.EXPORT_TAGS = {
    420            ":common": this.EXPORT,
    421            ":all": m.concat(this.EXPORT, this.EXPORT_OK)
    422        };
    423 
    424        m.nameFunctions(this);
    425    }
    426 });
    427 
    428 MochiKit.Selector.__new__();
    429 
    430 MochiKit.Base._exportSymbols(this, MochiKit.Selector);