fune/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
Kit Cambridge 3136ed3935 Bug 1396967 - Fix undeclared assignments in MozMill. r=tcsc
MozReview-Commit-ID: 7C5zFAHmRQQ

--HG--
extra : rebase_source : f6b0d5e1cac37ec1031176f291a13953f5af4e83
2017-09-05 12:32:36 -07:00

537 lines
13 KiB
JavaScript

/* 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];
let requirementPass = 0;
let 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);
};