diff options
Diffstat (limited to 'services/sync/tps/extensions/mozmill/resource/driver/elementslib.js')
-rw-r--r-- | services/sync/tps/extensions/mozmill/resource/driver/elementslib.js | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js b/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js new file mode 100644 index 000000000..4bf35a384 --- /dev/null +++ b/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js @@ -0,0 +1,537 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath", + "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib", + ]; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils); +var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings); +var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays); +var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2); +var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs); +var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom); +var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects); + +var countQuotes = function (str) { + var count = 0; + var i = 0; + + while (i < str.length) { + i = str.indexOf('"', i); + if (i != -1) { + count++; + i++; + } else { + break; + } + } + + return count; +}; + +/** + * smartSplit() + * + * Takes a lookup string as input and returns + * a list of each node in the string + */ +var smartSplit = function (str) { + // Ensure we have an even number of quotes + if (countQuotes(str) % 2 != 0) { + throw new Error ("Invalid Lookup Expression"); + } + + /** + * This regex matches a single "node" in a lookup string. + * In otherwords, it matches the part between the two '/'s + * + * Regex Explanation: + * \/ - start matching at the first forward slash + * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes) + * [^\/]* - match the remainder of text outside of last quote but before next slash + */ + var re = /\/([^\/"]*"[^"]*")*[^\/]*/g + var ret = [] + var match = re.exec(str); + + while (match != null) { + ret.push(match[0].replace(/^\//, "")); + match = re.exec(str); + } + + return ret; +}; + +/** + * defaultDocuments() + * + * Returns a list of default documents in which to search for elements + * if no document is provided + */ +function defaultDocuments() { + var win = Services.wm.getMostRecentWindow("navigator:browser"); + + return [ + win.document, + utils.getBrowserObject(win).selectedBrowser.contentWindow.document + ]; +}; + +/** + * nodeSearch() + * + * Takes an optional document, callback and locator string + * Returns a handle to the located element or null + */ +function nodeSearch(doc, func, string) { + if (doc != undefined) { + var documents = [doc]; + } else { + var documents = defaultDocuments(); + } + + var e = null; + var element = null; + + //inline function to recursively find the element in the DOM, cross frame. + var search = function (win, func, string) { + if (win == null) { + return; + } + + //do the lookup in the current window + element = func.call(win, string); + + if (!element || (element.length == 0)) { + var frames = win.frames; + for (var i = 0; i < frames.length; i++) { + search(frames[i], func, string); + } + } else { + e = element; + } + }; + + for (var i = 0; i < documents.length; ++i) { + var win = documents[i].defaultView; + search(win, func, string); + if (e) { + break; + } + } + + return e; +}; + +/** + * Selector() + * + * Finds an element by selector string + */ +function Selector(_document, selector, index) { + if (selector == undefined) { + throw new Error('Selector constructor did not recieve enough arguments.'); + } + + this.selector = selector; + + this.getNodeForDocument = function (s) { + return this.document.querySelectorAll(s); + }; + + var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector); + + return nodes ? nodes[index || 0] : null; +}; + +/** + * ID() + * + * Finds an element by ID + */ +function ID(_document, nodeID) { + if (nodeID == undefined) { + throw new Error('ID constructor did not recieve enough arguments.'); + } + + this.getNodeForDocument = function (nodeID) { + return this.document.getElementById(nodeID); + }; + + return nodeSearch(_document, this.getNodeForDocument, nodeID); +}; + +/** + * Link() + * + * Finds a link by innerHTML + */ +function Link(_document, linkName) { + if (linkName == undefined) { + throw new Error('Link constructor did not recieve enough arguments.'); + } + + this.getNodeForDocument = function (linkName) { + var getText = function (el) { + var text = ""; + + if (el.nodeType == 3) { //textNode + if (el.data != undefined) { + text = el.data; + } else { + text = el.innerHTML; + } + + text = text.replace(/n|r|t/g, " "); + } + else if (el.nodeType == 1) { //elementNode + for (var i = 0; i < el.childNodes.length; i++) { + var child = el.childNodes.item(i); + text += getText(child); + } + + if (el.tagName == "P" || el.tagName == "BR" || + el.tagName == "HR" || el.tagName == "DIV") { + text += "\n"; + } + } + + return text; + }; + + //sometimes the windows won't have this function + try { + var links = this.document.getElementsByTagName('a'); + } catch (e) { + // ADD LOG LINE mresults.write('Error: '+ e, 'lightred'); + } + + for (var i = 0; i < links.length; i++) { + var el = links[i]; + //if (getText(el).indexOf(this.linkName) != -1) { + if (el.innerHTML.indexOf(linkName) != -1) { + return el; + } + } + + return null; + }; + + return nodeSearch(_document, this.getNodeForDocument, linkName); +}; + +/** + * XPath() + * + * Finds an element by XPath + */ +function XPath(_document, expr) { + if (expr == undefined) { + throw new Error('XPath constructor did not recieve enough arguments.'); + } + + this.getNodeForDocument = function (s) { + var aNode = this.document; + var aExpr = s; + var xpe = null; + + if (this.document.defaultView == null) { + xpe = new getMethodInWindows('XPathEvaluator')(); + } else { + xpe = new this.document.defaultView.XPathEvaluator(); + } + + var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement + : aNode.ownerDocument.documentElement); + var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null); + var found = []; + var res; + + while (res = result.iterateNext()) { + found.push(res); + } + + return found[0]; + }; + + return nodeSearch(_document, this.getNodeForDocument, expr); +}; + +/** + * Name() + * + * Finds an element by Name + */ +function Name(_document, nName) { + if (nName == undefined) { + throw new Error('Name constructor did not recieve enough arguments.'); + } + + this.getNodeForDocument = function (s) { + try{ + var els = this.document.getElementsByName(s); + if (els.length > 0) { + return els[0]; + } + } catch (e) { + } + + return null; + }; + + return nodeSearch(_document, this.getNodeForDocument, nName); +}; + + +var _returnResult = function (results) { + if (results.length == 0) { + return null + } + else if (results.length == 1) { + return results[0]; + } else { + return results; + } +} + +var _forChildren = function (element, name, value) { + var results = []; + var nodes = Array.from(element.childNodes).filter(e => e); + + for (var i in nodes) { + var n = nodes[i]; + if (n[name] == value) { + results.push(n); + } + } + + return results; +} + +var _forAnonChildren = function (_document, element, name, value) { + var results = []; + var nodes = Array.from(_document.getAnoymousNodes(element)).filter(e => e); + + for (var i in nodes ) { + var n = nodes[i]; + if (n[name] == value) { + results.push(n); + } + } + + return results; +} + +var _byID = function (_document, parent, value) { + return _returnResult(_forChildren(parent, 'id', value)); +} + +var _byName = function (_document, parent, value) { + return _returnResult(_forChildren(parent, 'tagName', value)); +} + +var _byAttrib = function (parent, attributes) { + var results = []; + var nodes = parent.childNodes; + + for (var i in nodes) { + var n = nodes[i]; + requirementPass = 0; + requirementLength = 0; + + for (var a in attributes) { + requirementLength++; + try { + if (n.getAttribute(a) == attributes[a]) { + requirementPass++; + } + } catch (e) { + // Workaround any bugs in custom attribute crap in XUL elements + } + } + + if (requirementPass == requirementLength) { + results.push(n); + } + } + + return _returnResult(results) +} + +var _byAnonAttrib = function (_document, parent, attributes) { + var results = []; + + if (objects.getLength(attributes) == 1) { + for (var i in attributes) { + var k = i; + var v = attributes[i]; + } + + var result = _document.getAnonymousElementByAttribute(parent, k, v); + if (result) { + return result; + } + } + + var nodes = Array.from(_document.getAnonymousNodes(parent)).filter(n => n.getAttribute); + + function resultsForNodes (nodes) { + for (var i in nodes) { + var n = nodes[i]; + requirementPass = 0; + requirementLength = 0; + + for (var a in attributes) { + requirementLength++; + if (n.getAttribute(a) == attributes[a]) { + requirementPass++; + } + } + + if (requirementPass == requirementLength) { + results.push(n); + } + } + } + + resultsForNodes(nodes); + if (results.length == 0) { + resultsForNodes(Array.from(parent.childNodes).filter(n => n != undefined && n.getAttribute)); + } + + return _returnResult(results) +} + +var _byIndex = function (_document, parent, i) { + if (parent instanceof Array) { + return parent[i]; + } + + return parent.childNodes[i]; +} + +var _anonByName = function (_document, parent, value) { + return _returnResult(_forAnonChildren(_document, parent, 'tagName', value)); +} + +var _anonByAttrib = function (_document, parent, value) { + return _byAnonAttrib(_document, parent, value); +} + +var _anonByIndex = function (_document, parent, i) { + return _document.getAnonymousNodes(parent)[i]; +} + +/** + * Lookup() + * + * Finds an element by Lookup expression + */ +function Lookup(_document, expression) { + if (expression == undefined) { + throw new Error('Lookup constructor did not recieve enough arguments.'); + } + + var expSplit = smartSplit(expression).filter(e => e != ''); + expSplit.unshift(_document); + + var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex}; + var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex}; + + /** + * Reduces the lookup expression + * @param {Object} parentNode + * Parent node (previousValue of the formerly executed reduce callback) + * @param {String} exp + * Lookup expression for the parents child node + * + * @returns {Object} Node found by the given expression + */ + var reduceLookup = function (parentNode, exp) { + // Abort in case the parent node was not found + if (!parentNode) { + return false; + } + + // Handle case where only index is provided + var cases = nCases; + + // Handle ending index before any of the expression gets mangled + if (withs.endsWith(exp, ']')) { + var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']')); + } + + // Handle anon + if (withs.startsWith(exp, 'anon')) { + exp = strings.vslice(exp, '(', ')'); + cases = aCases; + } + + if (withs.startsWith(exp, '[')) { + try { + var obj = json2.JSON.parse(strings.vslice(exp, '[', ']')); + } catch (e) { + throw new SyntaxError(e + '. String to be parsed was || ' + + strings.vslice(exp, '[', ']') + ' ||'); + } + + var r = cases['index'](_document, parentNode, obj); + if (r == null) { + throw new SyntaxError('Expression "' + exp + + '" returned null. Anonymous == ' + (cases == aCases)); + } + + return r; + } + + for (var c in cases) { + if (withs.startsWith(exp, c)) { + try { + var obj = json2.JSON.parse(strings.vslice(exp, '(', ')')) + } catch (e) { + throw new SyntaxError(e + '. String to be parsed was || ' + + strings.vslice(exp, '(', ')') + ' ||'); + } + var result = cases[c](_document, parentNode, obj); + } + } + + if (!result) { + if (withs.startsWith(exp, '{')) { + try { + var obj = json2.JSON.parse(exp); + } catch (e) { + throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||'); + } + + if (cases == aCases) { + var result = _anonByAttrib(_document, parentNode, obj); + } else { + var result = _byAttrib(parentNode, obj); + } + } + } + + // Final return + if (expIndex) { + // TODO: Check length and raise error + return result[expIndex]; + } else { + // TODO: Check length and raise error + return result; + } + + // Maybe we should cause an exception here + return false; + }; + + return expSplit.reduce(reduceLookup); +}; |