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

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
     5 var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
     6                         "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
     7                        ];
     9 const Cc = Components.classes;
    10 const Ci = Components.interfaces;
    11 const Cu = Components.utils;
    13 Cu.import("resource://gre/modules/Services.jsm");
    15 var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
    16 var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
    17 var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
    18 var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
    19 var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
    20 var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
    21 var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
    23 var countQuotes = function (str) {
    24   var count = 0;
    25   var i = 0;
    27   while (i < str.length) {
    28     i = str.indexOf('"', i);
    29     if (i != -1) {
    30       count++;
    31       i++;
    32     } else {
    33       break;
    34     }
    35   }
    37   return count;
    38 };
    40 /**
    41  * smartSplit()
    42  *
    43  * Takes a lookup string as input and returns
    44  * a list of each node in the string
    45  */
    46 var smartSplit = function (str) {
    47   // Ensure we have an even number of quotes
    48   if (countQuotes(str) % 2 != 0) {
    49     throw new Error ("Invalid Lookup Expression");
    50   }
    52   /**
    53    * This regex matches a single "node" in a lookup string.
    54    * In otherwords, it matches the part between the two '/'s
    55    *
    56    * Regex Explanation:
    57    * \/ - start matching at the first forward slash
    58    * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
    59    * [^\/]* - match the remainder of text outside of last quote but before next slash
    60    */
    61   var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
    62   var ret = []
    63   var match = re.exec(str);
    65   while (match != null) {
    66     ret.push(match[0].replace(/^\//, ""));
    67     match = re.exec(str);
    68   }
    70   return ret;
    71 };
    73 /**
    74  * defaultDocuments()
    75  *
    76  * Returns a list of default documents in which to search for elements
    77  * if no document is provided
    78  */
    79 function defaultDocuments() {
    80   var win = Services.wm.getMostRecentWindow("navigator:browser");
    82   return [
    83     win.document,
    84     utils.getBrowserObject(win).selectedBrowser.contentWindow.document
    85   ];
    86 };
    88 /**
    89  * nodeSearch()
    90  *
    91  * Takes an optional document, callback and locator string
    92  * Returns a handle to the located element or null
    93  */
    94 function nodeSearch(doc, func, string) {
    95   if (doc != undefined) {
    96     var documents = [doc];
    97   } else {
    98     var documents = defaultDocuments();
    99   }
   101   var e = null;
   102   var element = null;
   104   //inline function to recursively find the element in the DOM, cross frame.
   105   var search = function (win, func, string) {
   106     if (win == null) {
   107       return;
   108     }
   110     //do the lookup in the current window
   111     element = func.call(win, string);
   113     if (!element || (element.length == 0)) {
   114       var frames = win.frames;
   115       for (var i = 0; i < frames.length; i++) {
   116         search(frames[i], func, string);
   117       }
   118     } else {
   119       e = element;
   120     }
   121   };
   123   for (var i = 0; i < documents.length; ++i) {
   124     var win = documents[i].defaultView;
   125     search(win, func, string);
   126     if (e) {
   127       break;
   128     }
   129   }
   131   return e;
   132 };
   134 /**
   135  * Selector()
   136  *
   137  * Finds an element by selector string
   138  */
   139 function Selector(_document, selector, index) {
   140   if (selector == undefined) {
   141     throw new Error('Selector constructor did not recieve enough arguments.');
   142   }
   144   this.selector = selector;
   146   this.getNodeForDocument = function (s) {
   147     return this.document.querySelectorAll(s);
   148   };
   150   var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
   152   return nodes ? nodes[index || 0] : null;
   153 };
   155 /**
   156  * ID()
   157  *
   158  * Finds an element by ID
   159  */
   160 function ID(_document, nodeID) {
   161   if (nodeID == undefined) {
   162     throw new Error('ID constructor did not recieve enough arguments.');
   163   }
   165   this.getNodeForDocument = function (nodeID) {
   166     return this.document.getElementById(nodeID);
   167   };
   169   return nodeSearch(_document, this.getNodeForDocument, nodeID);
   170 };
   172 /**
   173  * Link()
   174  *
   175  * Finds a link by innerHTML
   176  */
   177 function Link(_document, linkName) {
   178   if (linkName == undefined) {
   179     throw new Error('Link constructor did not recieve enough arguments.');
   180   }
   182   this.getNodeForDocument = function (linkName) {
   183     var getText = function (el) {
   184       var text = "";
   186       if (el.nodeType == 3) { //textNode
   187         if (el.data != undefined) {
   188           text = el.data;
   189         } else {
   190           text = el.innerHTML;
   191         }
   193         text = text.replace(/n|r|t/g, " ");
   194       }
   195       else if (el.nodeType == 1) { //elementNode
   196         for (var i = 0; i < el.childNodes.length; i++) {
   197           var child = el.childNodes.item(i);
   198           text += getText(child);
   199         }
   201         if (el.tagName == "P" || el.tagName == "BR" ||
   202             el.tagName == "HR" || el.tagName == "DIV") {
   203           text += "\n";
   204         }
   205       }
   207       return text;
   208     };
   210     //sometimes the windows won't have this function
   211     try {
   212       var links = this.document.getElementsByTagName('a');
   213     } catch (e) {
   214       // ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
   215     }
   217     for (var i = 0; i < links.length; i++) {
   218       var el = links[i];
   219       //if (getText(el).indexOf(this.linkName) != -1) {
   220       if (el.innerHTML.indexOf(linkName) != -1) {
   221         return el;
   222       }
   223     }
   225     return null;
   226   };
   228   return nodeSearch(_document, this.getNodeForDocument, linkName);
   229 };
   231 /**
   232  * XPath()
   233  *
   234  * Finds an element by XPath
   235  */
   236 function XPath(_document, expr) {
   237   if (expr == undefined) {
   238     throw new Error('XPath constructor did not recieve enough arguments.');
   239   }
   241   this.getNodeForDocument = function (s) {
   242     var aNode = this.document;
   243     var aExpr = s;
   244     var xpe = null;
   246     if (this.document.defaultView == null) {
   247       xpe = new getMethodInWindows('XPathEvaluator')();
   248     } else {
   249       xpe = new this.document.defaultView.XPathEvaluator();
   250     }
   252     var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
   253                                                                       : aNode.ownerDocument.documentElement);
   254     var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
   255     var found = [];
   256     var res;
   258     while (res = result.iterateNext()) {
   259       found.push(res);
   260     }
   262     return found[0];
   263   };
   265   return nodeSearch(_document, this.getNodeForDocument, expr);
   266 };
   268 /**
   269  * Name()
   270  *
   271  * Finds an element by Name
   272  */
   273 function Name(_document, nName) {
   274   if (nName == undefined) {
   275     throw new Error('Name constructor did not recieve enough arguments.');
   276   }
   278   this.getNodeForDocument = function (s) {
   279     try{
   280       var els = this.document.getElementsByName(s);
   281       if (els.length > 0) {
   282         return els[0];
   283       }
   284     } catch (e) {
   285     }
   287     return null;
   288   };
   290   return nodeSearch(_document, this.getNodeForDocument, nName);
   291 };
   294 var _returnResult = function (results) {
   295   if (results.length == 0) {
   296     return null
   297   }
   298   else if (results.length == 1) {
   299     return results[0];
   300   } else {
   301     return results;
   302   }
   303 }
   305 var _forChildren = function (element, name, value) {
   306   var results = [];
   307   var nodes = [e for each (e in element.childNodes) if (e)]
   309   for (var i in nodes) {
   310     var n = nodes[i];
   311     if (n[name] == value) {
   312       results.push(n);
   313     }
   314   }
   316   return results;
   317 }
   319 var _forAnonChildren = function (_document, element, name, value) {
   320   var results = [];
   321   var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
   323   for (var i in nodes ) {
   324     var n = nodes[i];
   325     if (n[name] == value) {
   326       results.push(n);
   327     }
   328   }
   330   return results;
   331 }
   333 var _byID = function (_document, parent, value) {
   334   return _returnResult(_forChildren(parent, 'id', value));
   335 }
   337 var _byName = function (_document, parent, value) {
   338   return _returnResult(_forChildren(parent, 'tagName', value));
   339 }
   341 var _byAttrib = function (parent, attributes) {
   342   var results = [];
   343   var nodes = parent.childNodes;
   345   for (var i in nodes) {
   346     var n = nodes[i];
   347     requirementPass = 0;
   348     requirementLength = 0;
   350     for (var a in attributes) {
   351       requirementLength++;
   352       try {
   353         if (n.getAttribute(a) == attributes[a]) {
   354           requirementPass++;
   355         }
   356       } catch (e) {
   357         // Workaround any bugs in custom attribute crap in XUL elements
   358       }
   359     }
   361     if (requirementPass == requirementLength) {
   362       results.push(n);
   363     }
   364   }
   366   return _returnResult(results)
   367 }
   369 var _byAnonAttrib = function (_document, parent, attributes) {
   370   var results = [];
   372   if (objects.getLength(attributes) == 1) {
   373     for (var i in attributes) {
   374       var k = i;
   375       var v = attributes[i];
   376     }
   378     var result = _document.getAnonymousElementByAttribute(parent, k, v);
   379     if (result) {
   380       return result;
   381     }
   382   }
   384   var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
   386   function resultsForNodes (nodes) {
   387     for (var i in nodes) {
   388       var n = nodes[i];
   389       requirementPass = 0;
   390       requirementLength = 0;
   392       for (var a in attributes) {
   393         requirementLength++;
   394         if (n.getAttribute(a) == attributes[a]) {
   395           requirementPass++;
   396         }
   397       }
   399       if (requirementPass == requirementLength) {
   400         results.push(n);
   401       }
   402     }
   403   }
   405   resultsForNodes(nodes);
   406   if (results.length == 0) {
   407     resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
   408   }
   410   return _returnResult(results)
   411 }
   413 var _byIndex = function (_document, parent, i) {
   414   if (parent instanceof Array) {
   415     return parent[i];
   416   }
   418   return parent.childNodes[i];
   419 }
   421 var _anonByName = function (_document, parent, value) {
   422   return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
   423 }
   425 var _anonByAttrib = function (_document, parent, value) {
   426   return _byAnonAttrib(_document, parent, value);
   427 }
   429 var _anonByIndex = function (_document, parent, i) {
   430   return _document.getAnonymousNodes(parent)[i];
   431 }
   433 /**
   434  * Lookup()
   435  *
   436  * Finds an element by Lookup expression
   437  */
   438 function Lookup(_document, expression) {
   439   if (expression == undefined) {
   440     throw new Error('Lookup constructor did not recieve enough arguments.');
   441   }
   443   var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
   444   expSplit.unshift(_document);
   446   var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
   447   var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
   449   /**
   450    * Reduces the lookup expression
   451    * @param {Object} parentNode
   452    *        Parent node (previousValue of the formerly executed reduce callback)
   453    * @param {String} exp
   454    *        Lookup expression for the parents child node
   455    *
   456    * @returns {Object} Node found by the given expression
   457    */
   458   var reduceLookup = function (parentNode, exp) {
   459     // Abort in case the parent node was not found
   460     if (!parentNode) {
   461       return false;
   462     }
   464     // Handle case where only index is provided
   465     var cases = nCases;
   467     // Handle ending index before any of the expression gets mangled
   468     if (withs.endsWith(exp, ']')) {
   469       var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
   470     }
   472     // Handle anon
   473     if (withs.startsWith(exp, 'anon')) {
   474       exp = strings.vslice(exp, '(', ')');
   475       cases = aCases;
   476     }
   478     if (withs.startsWith(exp, '[')) {
   479       try {
   480         var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
   481       } catch (e) {
   482         throw new SyntaxError(e + '. String to be parsed was || ' +
   483                               strings.vslice(exp, '[', ']') + ' ||');
   484       }
   486       var r = cases['index'](_document, parentNode, obj);
   487       if (r == null) {
   488         throw new SyntaxError('Expression "' + exp +
   489                               '" returned null. Anonymous == ' + (cases == aCases));
   490       }
   492       return r;
   493     }
   495     for (var c in cases) {
   496       if (withs.startsWith(exp, c)) {
   497         try {
   498           var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
   499         } catch (e) {
   500            throw new SyntaxError(e + '. String to be parsed was || ' +
   501                                  strings.vslice(exp, '(', ')') + '  ||');
   502         }
   503         var result = cases[c](_document, parentNode, obj);
   504       }
   505     }
   507     if (!result) {
   508       if (withs.startsWith(exp, '{')) {
   509         try {
   510           var obj = json2.JSON.parse(exp);
   511         } catch (e) {
   512           throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
   513         }
   515         if (cases == aCases) {
   516           var result = _anonByAttrib(_document, parentNode, obj);
   517         } else {
   518           var result = _byAttrib(parentNode, obj);
   519         }
   520       }
   521     }
   523     // Final return
   524     if (expIndex) {
   525       // TODO: Check length and raise error
   526       return result[expIndex];
   527     } else {
   528       // TODO: Check length and raise error
   529       return result;
   530     }
   532     // Maybe we should cause an exception here
   533     return false;
   534   };
   536   return expSplit.reduce(reduceLookup);
   537 };

mercurial