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

http://github.com/zpao/v8monkey · JavaScript · 478 lines · 438 code · 3 blank · 37 comment · 0 complexity · 960ed589d1fb250dfd853a88d9aefb3c MD5 · raw file

  1. // ***** BEGIN LICENSE BLOCK *****// ***** BEGIN LICENSE BLOCK *****
  2. // Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. //
  4. // The contents of this file are subject to the Mozilla Public License Version
  5. // 1.1 (the "License"); you may not use this file except in compliance with
  6. // the License. You may obtain a copy of the License at
  7. // http://www.mozilla.org/MPL/
  8. //
  9. // Software distributed under the License is distributed on an "AS IS" basis,
  10. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. // for the specific language governing rights and limitations under the
  12. // License.
  13. //
  14. // The Original Code is Mozilla Corporation Code.
  15. //
  16. // The Initial Developer of the Original Code is
  17. // Adam Christian.
  18. // Portions created by the Initial Developer are Copyright (C) 2008
  19. // the Initial Developer. All Rights Reserved.
  20. //
  21. // Contributor(s):
  22. // Adam Christian <adam.christian@gmail.com>
  23. // Mikeal Rogers <mikeal.rogers@gmail.com>
  24. //
  25. // Alternatively, the contents of this file may be used under the terms of
  26. // either the GNU General Public License Version 2 or later (the "GPL"), or
  27. // the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28. // in which case the provisions of the GPL or the LGPL are applicable instead
  29. // of those above. If you wish to allow use of your version of this file only
  30. // under the terms of either the GPL or the LGPL, and not to allow others to
  31. // use your version of this file under the terms of the MPL, indicate your
  32. // decision by deleting the provisions above and replace them with the notice
  33. // and other provisions required by the GPL or the LGPL. If you do not delete
  34. // the provisions above, a recipient may use your version of this file under
  35. // the terms of any one of the MPL, the GPL or the LGPL.
  36. //
  37. // ***** END LICENSE BLOCK *****
  38. var EXPORTED_SYMBOLS = ["Elem", "ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
  39. "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
  40. ];
  41. var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
  42. var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
  43. var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
  44. var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
  45. var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
  46. var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
  47. var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
  48. var countQuotes = function(str){
  49. var count = 0;
  50. var i = 0;
  51. while(i < str.length) {
  52. i = str.indexOf('"', i);
  53. if (i != -1) {
  54. count++;
  55. i++;
  56. } else {
  57. break;
  58. }
  59. }
  60. return count;
  61. };
  62. /**
  63. * smartSplit()
  64. *
  65. * Takes a lookup string as input and returns
  66. * a list of each node in the string
  67. */
  68. var smartSplit = function (str) {
  69. // Ensure we have an even number of quotes
  70. if (countQuotes(str) % 2 != 0) {
  71. throw new Error ("Invalid Lookup Expression");
  72. }
  73. /**
  74. * This regex matches a single "node" in a lookup string.
  75. * In otherwords, it matches the part between the two '/'s
  76. *
  77. * Regex Explanation:
  78. * \/ - start matching at the first forward slash
  79. * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
  80. * [^\/]* - match the remainder of text outside of last quote but before next slash
  81. */
  82. var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
  83. var ret = []
  84. var match = re.exec(str);
  85. while (match != null) {
  86. ret.push(match[0].replace(/^\//, ""));
  87. match = re.exec(str);
  88. }
  89. return ret;
  90. };
  91. /**
  92. * defaultDocuments()
  93. *
  94. * Returns a list of default documents in which to search for elements
  95. * if no document is provided
  96. */
  97. function defaultDocuments() {
  98. var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
  99. win = windowManager.getMostRecentWindow("navigator:browser");
  100. return [win.gBrowser.selectedBrowser.contentDocument, win.document];
  101. };
  102. /**
  103. * nodeSearch()
  104. *
  105. * Takes an optional document, callback and locator string
  106. * Returns a handle to the located element or null
  107. */
  108. function nodeSearch(doc, func, string) {
  109. if (doc != undefined) {
  110. var documents = [doc];
  111. } else {
  112. var documents = defaultDocuments();
  113. }
  114. var e = null;
  115. var element = null;
  116. //inline function to recursively find the element in the DOM, cross frame.
  117. var search = function(win, func, string) {
  118. if (win == null)
  119. return;
  120. //do the lookup in the current window
  121. element = func.call(win, string);
  122. if (!element || (element.length == 0)) {
  123. var frames = win.frames;
  124. for (var i=0; i < frames.length; i++) {
  125. search(frames[i], func, string);
  126. }
  127. }
  128. else { e = element; }
  129. };
  130. for (var i = 0; i < documents.length; ++i) {
  131. var win = documents[i].defaultView;
  132. search(win, func, string);
  133. if (e) break;
  134. }
  135. return e;
  136. };
  137. /**
  138. * Selector()
  139. *
  140. * Finds an element by selector string
  141. */
  142. function Selector(_document, selector, index) {
  143. if (selector == undefined) {
  144. throw new Error('Selector constructor did not recieve enough arguments.');
  145. }
  146. this.selector = selector;
  147. this.getNodeForDocument = function (s) {
  148. return this.document.querySelectorAll(s);
  149. };
  150. var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
  151. return nodes ? nodes[index || 0] : null;
  152. };
  153. /**
  154. * ID()
  155. *
  156. * Finds an element by ID
  157. */
  158. function ID(_document, nodeID) {
  159. if (nodeID == undefined) {
  160. throw new Error('ID constructor did not recieve enough arguments.');
  161. }
  162. this.getNodeForDocument = function (nodeID) {
  163. return this.document.getElementById(nodeID);
  164. };
  165. return nodeSearch(_document, this.getNodeForDocument, nodeID);
  166. };
  167. /**
  168. * Link()
  169. *
  170. * Finds a link by innerHTML
  171. */
  172. function Link(_document, linkName) {
  173. if (linkName == undefined) {
  174. throw new Error('Link constructor did not recieve enough arguments.');
  175. }
  176. this.getNodeForDocument = function (linkName) {
  177. var getText = function(el){
  178. var text = "";
  179. if (el.nodeType == 3){ //textNode
  180. if (el.data != undefined){
  181. text = el.data;
  182. } else {
  183. text = el.innerHTML;
  184. }
  185. text = text.replace(/n|r|t/g, " ");
  186. }
  187. if (el.nodeType == 1){ //elementNode
  188. for (var i = 0; i < el.childNodes.length; i++) {
  189. var child = el.childNodes.item(i);
  190. text += getText(child);
  191. }
  192. if (el.tagName == "P" || el.tagName == "BR" || el.tagName == "HR" || el.tagName == "DIV") {
  193. text += "n";
  194. }
  195. }
  196. return text;
  197. };
  198. //sometimes the windows won't have this function
  199. try {
  200. var links = this.document.getElementsByTagName('a'); }
  201. catch(err){ // ADD LOG LINE mresults.write('Error: '+ err, 'lightred');
  202. }
  203. for (var i = 0; i < links.length; i++) {
  204. var el = links[i];
  205. //if (getText(el).indexOf(this.linkName) != -1) {
  206. if (el.innerHTML.indexOf(linkName) != -1){
  207. return el;
  208. }
  209. }
  210. return null;
  211. };
  212. return nodeSearch(_document, this.getNodeForDocument, linkName);
  213. };
  214. /**
  215. * XPath()
  216. *
  217. * Finds an element by XPath
  218. */
  219. function XPath(_document, expr) {
  220. if (expr == undefined) {
  221. throw new Error('XPath constructor did not recieve enough arguments.');
  222. }
  223. this.getNodeForDocument = function (s) {
  224. var aNode = this.document;
  225. var aExpr = s;
  226. var xpe = null;
  227. if (this.document.defaultView == null) {
  228. xpe = new getMethodInWindows('XPathEvaluator')();
  229. } else {
  230. xpe = new this.document.defaultView.XPathEvaluator();
  231. }
  232. var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement : aNode.ownerDocument.documentElement);
  233. var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
  234. var found = [];
  235. var res;
  236. while (res = result.iterateNext())
  237. found.push(res);
  238. return found[0];
  239. };
  240. return nodeSearch(_document, this.getNodeForDocument, expr);
  241. };
  242. /**
  243. * Name()
  244. *
  245. * Finds an element by Name
  246. */
  247. function Name(_document, nName) {
  248. if (nName == undefined) {
  249. throw new Error('Name constructor did not recieve enough arguments.');
  250. }
  251. this.getNodeForDocument = function (s) {
  252. try{
  253. var els = this.document.getElementsByName(s);
  254. if (els.length > 0) { return els[0]; }
  255. }
  256. catch(err){};
  257. return null;
  258. };
  259. return nodeSearch(_document, this.getNodeForDocument, nName);
  260. };
  261. var _returnResult = function (results) {
  262. if (results.length == 0) {
  263. return null
  264. } else if (results.length == 1) {
  265. return results[0];
  266. } else {
  267. return results;
  268. }
  269. }
  270. var _forChildren = function (element, name, value) {
  271. var results = [];
  272. var nodes = [e for each (e in element.childNodes) if (e)]
  273. for (var i in nodes) {
  274. var n = nodes[i];
  275. if (n[name] == value) {
  276. results.push(n);
  277. }
  278. }
  279. return results;
  280. }
  281. var _forAnonChildren = function (_document, element, name, value) {
  282. var results = [];
  283. var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
  284. for (var i in nodes ) {
  285. var n = nodes[i];
  286. if (n[name] == value) {
  287. results.push(n);
  288. }
  289. }
  290. return results;
  291. }
  292. var _byID = function (_document, parent, value) {
  293. return _returnResult(_forChildren(parent, 'id', value));
  294. }
  295. var _byName = function (_document, parent, value) {
  296. return _returnResult(_forChildren(parent, 'tagName', value));
  297. }
  298. var _byAttrib = function (parent, attributes) {
  299. var results = [];
  300. var nodes = parent.childNodes;
  301. for (var i in nodes) {
  302. var n = nodes[i];
  303. requirementPass = 0;
  304. requirementLength = 0;
  305. for (var a in attributes) {
  306. requirementLength++;
  307. try {
  308. if (n.getAttribute(a) == attributes[a]) {
  309. requirementPass++;
  310. }
  311. } catch (err) {
  312. // Workaround any bugs in custom attribute crap in XUL elements
  313. }
  314. }
  315. if (requirementPass == requirementLength) {
  316. results.push(n);
  317. }
  318. }
  319. return _returnResult(results)
  320. }
  321. var _byAnonAttrib = function (_document, parent, attributes) {
  322. var results = [];
  323. if (objects.getLength(attributes) == 1) {
  324. for (var i in attributes) {var k = i; var v = attributes[i]; }
  325. var result = _document.getAnonymousElementByAttribute(parent, k, v)
  326. if (result) {
  327. return result;
  328. }
  329. }
  330. var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
  331. function resultsForNodes (nodes) {
  332. for (var i in nodes) {
  333. var n = nodes[i];
  334. requirementPass = 0;
  335. requirementLength = 0;
  336. for (var a in attributes) {
  337. requirementLength++;
  338. if (n.getAttribute(a) == attributes[a]) {
  339. requirementPass++;
  340. }
  341. }
  342. if (requirementPass == requirementLength) {
  343. results.push(n);
  344. }
  345. }
  346. }
  347. resultsForNodes(nodes)
  348. if (results.length == 0) {
  349. resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
  350. }
  351. return _returnResult(results)
  352. }
  353. var _byIndex = function (_document, parent, i) {
  354. if (parent instanceof Array) {
  355. return parent[i];
  356. }
  357. return parent.childNodes[i];
  358. }
  359. var _anonByName = function (_document, parent, value) {
  360. return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
  361. }
  362. var _anonByAttrib = function (_document, parent, value) {
  363. return _byAnonAttrib(_document, parent, value);
  364. }
  365. var _anonByIndex = function (_document, parent, i) {
  366. return _document.getAnonymousNodes(parent)[i];
  367. }
  368. /**
  369. * Lookup()
  370. *
  371. * Finds an element by Lookup expression
  372. */
  373. function Lookup (_document, expression) {
  374. if (expression == undefined) {
  375. throw new Error('Lookup constructor did not recieve enough arguments.');
  376. }
  377. var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
  378. expSplit.unshift(_document)
  379. var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
  380. var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
  381. var reduceLookup = function (parent, exp) {
  382. // Handle case where only index is provided
  383. var cases = nCases;
  384. // Handle ending index before any of the expression gets mangled
  385. if (withs.endsWith(exp, ']')) {
  386. var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
  387. }
  388. // Handle anon
  389. if (withs.startsWith(exp, 'anon')) {
  390. var exp = strings.vslice(exp, '(', ')');
  391. var cases = aCases;
  392. }
  393. if (withs.startsWith(exp, '[')) {
  394. try {
  395. var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
  396. } catch (err) {
  397. throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
  398. }
  399. var r = cases['index'](_document, parent, obj);
  400. if (r == null) {
  401. throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
  402. }
  403. return r;
  404. }
  405. for (var c in cases) {
  406. if (withs.startsWith(exp, c)) {
  407. try {
  408. var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
  409. } catch(err) {
  410. throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+' ||');
  411. }
  412. var result = cases[c](_document, parent, obj);
  413. }
  414. }
  415. if (!result) {
  416. if ( withs.startsWith(exp, '{') ) {
  417. try {
  418. var obj = json2.JSON.parse(exp)
  419. } catch(err) {
  420. throw new Error(err+'. String to be parsed was || '+exp+' ||');
  421. }
  422. if (cases == aCases) {
  423. var result = _anonByAttrib(_document, parent, obj)
  424. } else {
  425. var result = _byAttrib(parent, obj)
  426. }
  427. }
  428. if (!result) {
  429. throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
  430. }
  431. }
  432. // Final return
  433. if (expIndex) {
  434. // TODO: Check length and raise error
  435. return result[expIndex];
  436. } else {
  437. // TODO: Check length and raise error
  438. return result;
  439. }
  440. // Maybe we should cause an exception here
  441. return false;
  442. };
  443. return expSplit.reduce(reduceLookup);
  444. };