services/sync/tps/extensions/mozmill/resource/driver/elementslib.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
michael@0 6 "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
michael@0 7 ];
michael@0 8
michael@0 9 const Cc = Components.classes;
michael@0 10 const Ci = Components.interfaces;
michael@0 11 const Cu = Components.utils;
michael@0 12
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14
michael@0 15 var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
michael@0 16 var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
michael@0 17 var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
michael@0 18 var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
michael@0 19 var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
michael@0 20 var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
michael@0 21 var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
michael@0 22
michael@0 23 var countQuotes = function (str) {
michael@0 24 var count = 0;
michael@0 25 var i = 0;
michael@0 26
michael@0 27 while (i < str.length) {
michael@0 28 i = str.indexOf('"', i);
michael@0 29 if (i != -1) {
michael@0 30 count++;
michael@0 31 i++;
michael@0 32 } else {
michael@0 33 break;
michael@0 34 }
michael@0 35 }
michael@0 36
michael@0 37 return count;
michael@0 38 };
michael@0 39
michael@0 40 /**
michael@0 41 * smartSplit()
michael@0 42 *
michael@0 43 * Takes a lookup string as input and returns
michael@0 44 * a list of each node in the string
michael@0 45 */
michael@0 46 var smartSplit = function (str) {
michael@0 47 // Ensure we have an even number of quotes
michael@0 48 if (countQuotes(str) % 2 != 0) {
michael@0 49 throw new Error ("Invalid Lookup Expression");
michael@0 50 }
michael@0 51
michael@0 52 /**
michael@0 53 * This regex matches a single "node" in a lookup string.
michael@0 54 * In otherwords, it matches the part between the two '/'s
michael@0 55 *
michael@0 56 * Regex Explanation:
michael@0 57 * \/ - start matching at the first forward slash
michael@0 58 * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
michael@0 59 * [^\/]* - match the remainder of text outside of last quote but before next slash
michael@0 60 */
michael@0 61 var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
michael@0 62 var ret = []
michael@0 63 var match = re.exec(str);
michael@0 64
michael@0 65 while (match != null) {
michael@0 66 ret.push(match[0].replace(/^\//, ""));
michael@0 67 match = re.exec(str);
michael@0 68 }
michael@0 69
michael@0 70 return ret;
michael@0 71 };
michael@0 72
michael@0 73 /**
michael@0 74 * defaultDocuments()
michael@0 75 *
michael@0 76 * Returns a list of default documents in which to search for elements
michael@0 77 * if no document is provided
michael@0 78 */
michael@0 79 function defaultDocuments() {
michael@0 80 var win = Services.wm.getMostRecentWindow("navigator:browser");
michael@0 81
michael@0 82 return [
michael@0 83 win.document,
michael@0 84 utils.getBrowserObject(win).selectedBrowser.contentWindow.document
michael@0 85 ];
michael@0 86 };
michael@0 87
michael@0 88 /**
michael@0 89 * nodeSearch()
michael@0 90 *
michael@0 91 * Takes an optional document, callback and locator string
michael@0 92 * Returns a handle to the located element or null
michael@0 93 */
michael@0 94 function nodeSearch(doc, func, string) {
michael@0 95 if (doc != undefined) {
michael@0 96 var documents = [doc];
michael@0 97 } else {
michael@0 98 var documents = defaultDocuments();
michael@0 99 }
michael@0 100
michael@0 101 var e = null;
michael@0 102 var element = null;
michael@0 103
michael@0 104 //inline function to recursively find the element in the DOM, cross frame.
michael@0 105 var search = function (win, func, string) {
michael@0 106 if (win == null) {
michael@0 107 return;
michael@0 108 }
michael@0 109
michael@0 110 //do the lookup in the current window
michael@0 111 element = func.call(win, string);
michael@0 112
michael@0 113 if (!element || (element.length == 0)) {
michael@0 114 var frames = win.frames;
michael@0 115 for (var i = 0; i < frames.length; i++) {
michael@0 116 search(frames[i], func, string);
michael@0 117 }
michael@0 118 } else {
michael@0 119 e = element;
michael@0 120 }
michael@0 121 };
michael@0 122
michael@0 123 for (var i = 0; i < documents.length; ++i) {
michael@0 124 var win = documents[i].defaultView;
michael@0 125 search(win, func, string);
michael@0 126 if (e) {
michael@0 127 break;
michael@0 128 }
michael@0 129 }
michael@0 130
michael@0 131 return e;
michael@0 132 };
michael@0 133
michael@0 134 /**
michael@0 135 * Selector()
michael@0 136 *
michael@0 137 * Finds an element by selector string
michael@0 138 */
michael@0 139 function Selector(_document, selector, index) {
michael@0 140 if (selector == undefined) {
michael@0 141 throw new Error('Selector constructor did not recieve enough arguments.');
michael@0 142 }
michael@0 143
michael@0 144 this.selector = selector;
michael@0 145
michael@0 146 this.getNodeForDocument = function (s) {
michael@0 147 return this.document.querySelectorAll(s);
michael@0 148 };
michael@0 149
michael@0 150 var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
michael@0 151
michael@0 152 return nodes ? nodes[index || 0] : null;
michael@0 153 };
michael@0 154
michael@0 155 /**
michael@0 156 * ID()
michael@0 157 *
michael@0 158 * Finds an element by ID
michael@0 159 */
michael@0 160 function ID(_document, nodeID) {
michael@0 161 if (nodeID == undefined) {
michael@0 162 throw new Error('ID constructor did not recieve enough arguments.');
michael@0 163 }
michael@0 164
michael@0 165 this.getNodeForDocument = function (nodeID) {
michael@0 166 return this.document.getElementById(nodeID);
michael@0 167 };
michael@0 168
michael@0 169 return nodeSearch(_document, this.getNodeForDocument, nodeID);
michael@0 170 };
michael@0 171
michael@0 172 /**
michael@0 173 * Link()
michael@0 174 *
michael@0 175 * Finds a link by innerHTML
michael@0 176 */
michael@0 177 function Link(_document, linkName) {
michael@0 178 if (linkName == undefined) {
michael@0 179 throw new Error('Link constructor did not recieve enough arguments.');
michael@0 180 }
michael@0 181
michael@0 182 this.getNodeForDocument = function (linkName) {
michael@0 183 var getText = function (el) {
michael@0 184 var text = "";
michael@0 185
michael@0 186 if (el.nodeType == 3) { //textNode
michael@0 187 if (el.data != undefined) {
michael@0 188 text = el.data;
michael@0 189 } else {
michael@0 190 text = el.innerHTML;
michael@0 191 }
michael@0 192
michael@0 193 text = text.replace(/n|r|t/g, " ");
michael@0 194 }
michael@0 195 else if (el.nodeType == 1) { //elementNode
michael@0 196 for (var i = 0; i < el.childNodes.length; i++) {
michael@0 197 var child = el.childNodes.item(i);
michael@0 198 text += getText(child);
michael@0 199 }
michael@0 200
michael@0 201 if (el.tagName == "P" || el.tagName == "BR" ||
michael@0 202 el.tagName == "HR" || el.tagName == "DIV") {
michael@0 203 text += "\n";
michael@0 204 }
michael@0 205 }
michael@0 206
michael@0 207 return text;
michael@0 208 };
michael@0 209
michael@0 210 //sometimes the windows won't have this function
michael@0 211 try {
michael@0 212 var links = this.document.getElementsByTagName('a');
michael@0 213 } catch (e) {
michael@0 214 // ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
michael@0 215 }
michael@0 216
michael@0 217 for (var i = 0; i < links.length; i++) {
michael@0 218 var el = links[i];
michael@0 219 //if (getText(el).indexOf(this.linkName) != -1) {
michael@0 220 if (el.innerHTML.indexOf(linkName) != -1) {
michael@0 221 return el;
michael@0 222 }
michael@0 223 }
michael@0 224
michael@0 225 return null;
michael@0 226 };
michael@0 227
michael@0 228 return nodeSearch(_document, this.getNodeForDocument, linkName);
michael@0 229 };
michael@0 230
michael@0 231 /**
michael@0 232 * XPath()
michael@0 233 *
michael@0 234 * Finds an element by XPath
michael@0 235 */
michael@0 236 function XPath(_document, expr) {
michael@0 237 if (expr == undefined) {
michael@0 238 throw new Error('XPath constructor did not recieve enough arguments.');
michael@0 239 }
michael@0 240
michael@0 241 this.getNodeForDocument = function (s) {
michael@0 242 var aNode = this.document;
michael@0 243 var aExpr = s;
michael@0 244 var xpe = null;
michael@0 245
michael@0 246 if (this.document.defaultView == null) {
michael@0 247 xpe = new getMethodInWindows('XPathEvaluator')();
michael@0 248 } else {
michael@0 249 xpe = new this.document.defaultView.XPathEvaluator();
michael@0 250 }
michael@0 251
michael@0 252 var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
michael@0 253 : aNode.ownerDocument.documentElement);
michael@0 254 var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
michael@0 255 var found = [];
michael@0 256 var res;
michael@0 257
michael@0 258 while (res = result.iterateNext()) {
michael@0 259 found.push(res);
michael@0 260 }
michael@0 261
michael@0 262 return found[0];
michael@0 263 };
michael@0 264
michael@0 265 return nodeSearch(_document, this.getNodeForDocument, expr);
michael@0 266 };
michael@0 267
michael@0 268 /**
michael@0 269 * Name()
michael@0 270 *
michael@0 271 * Finds an element by Name
michael@0 272 */
michael@0 273 function Name(_document, nName) {
michael@0 274 if (nName == undefined) {
michael@0 275 throw new Error('Name constructor did not recieve enough arguments.');
michael@0 276 }
michael@0 277
michael@0 278 this.getNodeForDocument = function (s) {
michael@0 279 try{
michael@0 280 var els = this.document.getElementsByName(s);
michael@0 281 if (els.length > 0) {
michael@0 282 return els[0];
michael@0 283 }
michael@0 284 } catch (e) {
michael@0 285 }
michael@0 286
michael@0 287 return null;
michael@0 288 };
michael@0 289
michael@0 290 return nodeSearch(_document, this.getNodeForDocument, nName);
michael@0 291 };
michael@0 292
michael@0 293
michael@0 294 var _returnResult = function (results) {
michael@0 295 if (results.length == 0) {
michael@0 296 return null
michael@0 297 }
michael@0 298 else if (results.length == 1) {
michael@0 299 return results[0];
michael@0 300 } else {
michael@0 301 return results;
michael@0 302 }
michael@0 303 }
michael@0 304
michael@0 305 var _forChildren = function (element, name, value) {
michael@0 306 var results = [];
michael@0 307 var nodes = [e for each (e in element.childNodes) if (e)]
michael@0 308
michael@0 309 for (var i in nodes) {
michael@0 310 var n = nodes[i];
michael@0 311 if (n[name] == value) {
michael@0 312 results.push(n);
michael@0 313 }
michael@0 314 }
michael@0 315
michael@0 316 return results;
michael@0 317 }
michael@0 318
michael@0 319 var _forAnonChildren = function (_document, element, name, value) {
michael@0 320 var results = [];
michael@0 321 var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
michael@0 322
michael@0 323 for (var i in nodes ) {
michael@0 324 var n = nodes[i];
michael@0 325 if (n[name] == value) {
michael@0 326 results.push(n);
michael@0 327 }
michael@0 328 }
michael@0 329
michael@0 330 return results;
michael@0 331 }
michael@0 332
michael@0 333 var _byID = function (_document, parent, value) {
michael@0 334 return _returnResult(_forChildren(parent, 'id', value));
michael@0 335 }
michael@0 336
michael@0 337 var _byName = function (_document, parent, value) {
michael@0 338 return _returnResult(_forChildren(parent, 'tagName', value));
michael@0 339 }
michael@0 340
michael@0 341 var _byAttrib = function (parent, attributes) {
michael@0 342 var results = [];
michael@0 343 var nodes = parent.childNodes;
michael@0 344
michael@0 345 for (var i in nodes) {
michael@0 346 var n = nodes[i];
michael@0 347 requirementPass = 0;
michael@0 348 requirementLength = 0;
michael@0 349
michael@0 350 for (var a in attributes) {
michael@0 351 requirementLength++;
michael@0 352 try {
michael@0 353 if (n.getAttribute(a) == attributes[a]) {
michael@0 354 requirementPass++;
michael@0 355 }
michael@0 356 } catch (e) {
michael@0 357 // Workaround any bugs in custom attribute crap in XUL elements
michael@0 358 }
michael@0 359 }
michael@0 360
michael@0 361 if (requirementPass == requirementLength) {
michael@0 362 results.push(n);
michael@0 363 }
michael@0 364 }
michael@0 365
michael@0 366 return _returnResult(results)
michael@0 367 }
michael@0 368
michael@0 369 var _byAnonAttrib = function (_document, parent, attributes) {
michael@0 370 var results = [];
michael@0 371
michael@0 372 if (objects.getLength(attributes) == 1) {
michael@0 373 for (var i in attributes) {
michael@0 374 var k = i;
michael@0 375 var v = attributes[i];
michael@0 376 }
michael@0 377
michael@0 378 var result = _document.getAnonymousElementByAttribute(parent, k, v);
michael@0 379 if (result) {
michael@0 380 return result;
michael@0 381 }
michael@0 382 }
michael@0 383
michael@0 384 var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
michael@0 385
michael@0 386 function resultsForNodes (nodes) {
michael@0 387 for (var i in nodes) {
michael@0 388 var n = nodes[i];
michael@0 389 requirementPass = 0;
michael@0 390 requirementLength = 0;
michael@0 391
michael@0 392 for (var a in attributes) {
michael@0 393 requirementLength++;
michael@0 394 if (n.getAttribute(a) == attributes[a]) {
michael@0 395 requirementPass++;
michael@0 396 }
michael@0 397 }
michael@0 398
michael@0 399 if (requirementPass == requirementLength) {
michael@0 400 results.push(n);
michael@0 401 }
michael@0 402 }
michael@0 403 }
michael@0 404
michael@0 405 resultsForNodes(nodes);
michael@0 406 if (results.length == 0) {
michael@0 407 resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
michael@0 408 }
michael@0 409
michael@0 410 return _returnResult(results)
michael@0 411 }
michael@0 412
michael@0 413 var _byIndex = function (_document, parent, i) {
michael@0 414 if (parent instanceof Array) {
michael@0 415 return parent[i];
michael@0 416 }
michael@0 417
michael@0 418 return parent.childNodes[i];
michael@0 419 }
michael@0 420
michael@0 421 var _anonByName = function (_document, parent, value) {
michael@0 422 return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
michael@0 423 }
michael@0 424
michael@0 425 var _anonByAttrib = function (_document, parent, value) {
michael@0 426 return _byAnonAttrib(_document, parent, value);
michael@0 427 }
michael@0 428
michael@0 429 var _anonByIndex = function (_document, parent, i) {
michael@0 430 return _document.getAnonymousNodes(parent)[i];
michael@0 431 }
michael@0 432
michael@0 433 /**
michael@0 434 * Lookup()
michael@0 435 *
michael@0 436 * Finds an element by Lookup expression
michael@0 437 */
michael@0 438 function Lookup(_document, expression) {
michael@0 439 if (expression == undefined) {
michael@0 440 throw new Error('Lookup constructor did not recieve enough arguments.');
michael@0 441 }
michael@0 442
michael@0 443 var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
michael@0 444 expSplit.unshift(_document);
michael@0 445
michael@0 446 var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
michael@0 447 var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
michael@0 448
michael@0 449 /**
michael@0 450 * Reduces the lookup expression
michael@0 451 * @param {Object} parentNode
michael@0 452 * Parent node (previousValue of the formerly executed reduce callback)
michael@0 453 * @param {String} exp
michael@0 454 * Lookup expression for the parents child node
michael@0 455 *
michael@0 456 * @returns {Object} Node found by the given expression
michael@0 457 */
michael@0 458 var reduceLookup = function (parentNode, exp) {
michael@0 459 // Abort in case the parent node was not found
michael@0 460 if (!parentNode) {
michael@0 461 return false;
michael@0 462 }
michael@0 463
michael@0 464 // Handle case where only index is provided
michael@0 465 var cases = nCases;
michael@0 466
michael@0 467 // Handle ending index before any of the expression gets mangled
michael@0 468 if (withs.endsWith(exp, ']')) {
michael@0 469 var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
michael@0 470 }
michael@0 471
michael@0 472 // Handle anon
michael@0 473 if (withs.startsWith(exp, 'anon')) {
michael@0 474 exp = strings.vslice(exp, '(', ')');
michael@0 475 cases = aCases;
michael@0 476 }
michael@0 477
michael@0 478 if (withs.startsWith(exp, '[')) {
michael@0 479 try {
michael@0 480 var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
michael@0 481 } catch (e) {
michael@0 482 throw new SyntaxError(e + '. String to be parsed was || ' +
michael@0 483 strings.vslice(exp, '[', ']') + ' ||');
michael@0 484 }
michael@0 485
michael@0 486 var r = cases['index'](_document, parentNode, obj);
michael@0 487 if (r == null) {
michael@0 488 throw new SyntaxError('Expression "' + exp +
michael@0 489 '" returned null. Anonymous == ' + (cases == aCases));
michael@0 490 }
michael@0 491
michael@0 492 return r;
michael@0 493 }
michael@0 494
michael@0 495 for (var c in cases) {
michael@0 496 if (withs.startsWith(exp, c)) {
michael@0 497 try {
michael@0 498 var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
michael@0 499 } catch (e) {
michael@0 500 throw new SyntaxError(e + '. String to be parsed was || ' +
michael@0 501 strings.vslice(exp, '(', ')') + ' ||');
michael@0 502 }
michael@0 503 var result = cases[c](_document, parentNode, obj);
michael@0 504 }
michael@0 505 }
michael@0 506
michael@0 507 if (!result) {
michael@0 508 if (withs.startsWith(exp, '{')) {
michael@0 509 try {
michael@0 510 var obj = json2.JSON.parse(exp);
michael@0 511 } catch (e) {
michael@0 512 throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
michael@0 513 }
michael@0 514
michael@0 515 if (cases == aCases) {
michael@0 516 var result = _anonByAttrib(_document, parentNode, obj);
michael@0 517 } else {
michael@0 518 var result = _byAttrib(parentNode, obj);
michael@0 519 }
michael@0 520 }
michael@0 521 }
michael@0 522
michael@0 523 // Final return
michael@0 524 if (expIndex) {
michael@0 525 // TODO: Check length and raise error
michael@0 526 return result[expIndex];
michael@0 527 } else {
michael@0 528 // TODO: Check length and raise error
michael@0 529 return result;
michael@0 530 }
michael@0 531
michael@0 532 // Maybe we should cause an exception here
michael@0 533 return false;
michael@0 534 };
michael@0 535
michael@0 536 return expSplit.reduce(reduceLookup);
michael@0 537 };

mercurial