PageRenderTime 62ms CodeModel.GetById 17ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

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