summaryrefslogtreecommitdiffstats
path: root/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tps/extensions/mozmill/resource/driver/elementslib.js')
-rw-r--r--services/sync/tps/extensions/mozmill/resource/driver/elementslib.js537
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);
+};