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

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial