gecko/services/sync/tps/extensions/mozmill/resource/modules/elementslib.js
Gervase Markham d4eb7d5782 Bug 759095 - upgrade license to MPL 2, and other licensing cleanups.
--HG--
extra : rebase_source : da55a4937383eda2baf7c9a362501da8ee664146
2012-05-29 16:52:43 +01:00

445 lines
12 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 = ["Elem", "ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
"Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
];
var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
var objects = {}; Components.utils.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 windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
win = windowManager.getMostRecentWindow("navigator:browser");
return [win.gBrowser.selectedBrowser.contentDocument, win.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, " ");
}
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(err){ // ADD LOG LINE mresults.write('Error: '+ err, '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(err){};
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 = [e for each (e in element.childNodes) if (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 = [e for each (e in _document.getAnoymousNodes(element)) if (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 (err) {
// 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 = [n for each (n in _document.getAnonymousNodes(parent)) if (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([n for each (n in parent.childNodes) if (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 = [e for each (e in smartSplit(expression) ) if (e != '')];
expSplit.unshift(_document)
var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
var reduceLookup = function (parent, exp) {
// 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')) {
var exp = strings.vslice(exp, '(', ')');
var cases = aCases;
}
if (withs.startsWith(exp, '[')) {
try {
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
} catch (err) {
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
}
var r = cases['index'](_document, parent, obj);
if (r == null) {
throw new Error('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(err) {
throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+' ||');
}
var result = cases[c](_document, parent, obj);
}
}
if (!result) {
if ( withs.startsWith(exp, '{') ) {
try {
var obj = json2.JSON.parse(exp)
} catch(err) {
throw new Error(err+'. String to be parsed was || '+exp+' ||');
}
if (cases == aCases) {
var result = _anonByAttrib(_document, parent, obj)
} else {
var result = _byAttrib(parent, obj)
}
}
if (!result) {
throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
}
}
// 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);
};