1 /* Licensed to the Apache Software Foundation (ASF) under one or more
  2  * contributor license agreements.  See the NOTICE file distributed with
  3  * this work for additional information regarding copyright ownership.
  4  * The ASF licenses this file to you under the Apache License, Version 2.0
  5  * (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  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 
 17 /**
 18  * @class
 19  * @name _Dom
 20  * @memberOf myfaces._impl._util
 21  * @extends myfaces._impl.core._Runtime
 22  * @description Object singleton collection of dom helper routines
 23  * (which in later incarnations will
 24  * get browser specific speed optimizations)
 25  *
 26  * Since we have to be as tight as possible
 27  * we will focus with our dom routines to only
 28  * the parts which our impl uses.
 29  * A jquery like query API would be nice
 30  * but this would increase up our codebase significantly
 31  *
 32  * <p>This class provides the proper fallbacks for ie8- and Firefox 3.6-</p>
 33  */
 34 _MF_SINGLTN(_PFX_UTIL + "_Dom", Object, /** @lends myfaces._impl._util._Dom.prototype */ {
 35 
 36     /*table elements which are used in various parts */
 37     TABLE_ELEMS:  {
 38         "thead": 1,
 39         "tbody": 1,
 40         "tr": 1,
 41         "th": 1,
 42         "td": 1,
 43         "tfoot" : 1
 44     },
 45 
 46     _Lang:  myfaces._impl._util._Lang,
 47     _RT:    myfaces._impl.core._Runtime,
 48     _dummyPlaceHolder:null,
 49 
 50     /**
 51      * standard constructor
 52      */
 53     constructor_: function() {
 54     },
 55 
 56     runCss: function(item/*, xmlData*/) {
 57 
 58         var  UDEF = "undefined",
 59                 _RT = this._RT,
 60                 _Lang = this._Lang,
 61                 applyStyle = function(item, style) {
 62                     var newSS = document.createElement("style");
 63 
 64                     newSS.setAttribute("rel", item.getAttribute("rel") || "stylesheet");
 65                     newSS.setAttribute("type", item.getAttribute("type") || "text/css");
 66                     document.getElementsByTagName("head")[0].appendChild(newSS);
 67                     //ie merrily again goes its own way
 68                     if (window.attachEvent && !_RT.isOpera && UDEF != typeof newSS.styleSheet && UDEF != newSS.styleSheet.cssText) newSS.styleSheet.cssText = style;
 69                     else newSS.appendChild(document.createTextNode(style));
 70                 },
 71 
 72                 execCss = function(item) {
 73                     var equalsIgnoreCase = _Lang.equalsIgnoreCase;
 74                     var tagName = item.tagName;
 75                     if (tagName && equalsIgnoreCase(tagName, "link") && equalsIgnoreCase(item.getAttribute("type"), "text/css")) {
 76                         applyStyle(item, "@import url('" + item.getAttribute("href") + "');");
 77                     } else if (tagName && equalsIgnoreCase(tagName, "style") && equalsIgnoreCase(item.getAttribute("type"), "text/css")) {
 78                         var innerText = [];
 79                         //compliant browsers know child nodes
 80                         var childNodes = item.childNodes;
 81                         if (childNodes) {
 82                             var len = childNodes.length;
 83                             for (var cnt = 0; cnt < len; cnt++) {
 84                                 innerText.push(childNodes[cnt].innerHTML || childNodes[cnt].data);
 85                             }
 86                             //non compliant ones innerHTML
 87                         } else if (item.innerHTML) {
 88                             innerText.push(item.innerHTML);
 89                         }
 90 
 91                         applyStyle(item, innerText.join(""));
 92                     }
 93                 };
 94 
 95         try {
 96             var scriptElements = this.findByTagNames(item, {"link":1,"style":1}, true);
 97             if (scriptElements == null) return;
 98             for (var cnt = 0; cnt < scriptElements.length; cnt++) {
 99                 execCss(scriptElements[cnt]);
100             }
101 
102         } finally {
103             //the usual ie6 fix code
104             //the IE6 garbage collector is broken
105             //nulling closures helps somewhat to reduce
106             //mem leaks, which are impossible to avoid
107             //at this browser
108             execCss = null;
109             applyStyle = null;
110         }
111     },
112 
113 
114     /**
115      * Run through the given Html item and execute the inline scripts
116      * (IE doesn't do this by itself)
117      * @param {Node} item
118      */
119     runScripts: function(item, xmlData) {
120         var _Lang = this._Lang,
121             _RT = this._RT,
122             finalScripts = [],
123             execScrpt = function(item) {
124                 var tagName = item.tagName;
125                 if (tagName && _Lang.equalsIgnoreCase(tagName, "script")) {
126                     var src = item.getAttribute('src');
127                     if ('undefined' != typeof src
128                             && null != src
129                             && src.length > 0
130                             ) {
131                         //we have to move this into an inner if because chrome otherwise chokes
132                         //due to changing the and order instead of relying on left to right
133                         //if jsf.js is already registered we do not replace it anymore
134                         if ((src.indexOf("ln=scripts") == -1 && src.indexOf("ln=javax.faces") == -1) || (src.indexOf("/jsf.js") == -1
135                                 && src.indexOf("/jsf-uncompressed.js") == -1)) {
136                             if (finalScripts.length) {
137                                 //script source means we have to eval the existing
138                                 //scripts before running the include
139                                 _RT.globalEval(finalScripts.join("\n"));
140 
141                                 finalScripts = [];
142                             }
143                             _RT.loadScriptEval(src, item.getAttribute('type'), false, "UTF-8", false);
144                         }
145                         //TODO handle embedded scripts
146                     } else {
147                         // embedded script auto eval
148                         var test = (!xmlData) ? item.text : _Lang.serializeChilds(item);
149                         var go = true;
150                         while (go) {
151                             go = false;
152                             if (test.substring(0, 1) == " ") {
153                                 test = test.substring(1);
154                                 go = true;
155                             }
156                             if (test.substring(0, 4) == "<!--") {
157                                 test = test.substring(4);
158                                 go = true;
159                             }
160                             if (test.substring(0, 11) == "//<![CDATA[") {
161                                 test = test.substring(11);
162                                 go = true;
163                             }
164                         }
165                         // we have to run the script under a global context
166                         //we store the script for less calls to eval
167                         finalScripts.push(test);
168 
169                     }
170                 }
171             };
172         try {
173             var scriptElements = this.findByTagName(item, "script", true);
174             if (scriptElements == null) return;
175             for (var cnt = 0; cnt < scriptElements.length; cnt++) {
176                 execScrpt(scriptElements[cnt]);
177             }
178             if (finalScripts.length) {
179                 _RT.globalEval(finalScripts.join("\n"));
180             }
181         } finally {
182             //the usual ie6 fix code
183             //the IE6 garbage collector is broken
184             //nulling closures helps somewhat to reduce
185             //mem leaks, which are impossible to avoid
186             //at this browser
187             execScrpt = null;
188         }
189     },
190 
191 
192     /**
193      * determines to fetch a node
194      * from its id or name, the name case
195      * only works if the element is unique in its name
196      * @param {String} elem
197      */
198     byIdOrName: function(elem) {
199         if (!elem) return null;
200         if (!this._Lang.isString(elem)) return elem;
201 
202         var ret = this.byId(elem);
203         if (ret) return ret;
204         //we try the unique name fallback
205         var items = document.getElementsByName(elem);
206         return ((items.length == 1) ? items[0] : null);
207     },
208 
209     /**
210      * node id or name, determines the valid form identifier of a node
211      * depending on its uniqueness
212      *
213      * Usually the id is chosen for an elem, but if the id does not
214      * exist we try a name fallback. If the passed element has a unique
215      * name we can use that one as subsequent identifier.
216      *
217      *
218      * @param {String} elem
219      */
220     nodeIdOrName: function(elem) {
221         if (elem) {
222             //just to make sure that the pas
223 
224             elem = this.byId(elem);
225             if (!elem) return null;
226             //detached element handling, we also store the element name
227             //to get a fallback option in case the identifier is not determinable
228             // anymore, in case of a framework induced detachment the element.name should
229             // be shared if the identifier is not determinable anymore
230             //the downside of this method is the element name must be unique
231             //which in case of jsf it is
232             var elementId = elem.id || elem.name;
233             if ((elem.id == null || elem.id == '') && elem.name) {
234                 elementId = elem.name;
235 
236                 //last check for uniqueness
237                 if (this.getElementsByName(elementId).length > 1) {
238                     //no unique element name so we need to perform
239                     //a return null to let the caller deal with this issue
240                     return null;
241                 }
242             }
243             return elementId;
244         }
245         return null;
246     },
247 
248     deleteItems: function(items) {
249         if (! items || ! items.length) return;
250         for (var cnt = 0; cnt < items.length; cnt++) {
251             this.deleteItem(items[cnt]);
252         }
253     },
254 
255     /**
256      * Simple delete on an existing item
257      */
258     deleteItem: function(itemIdToReplace) {
259         var item = this.byId(itemIdToReplace);
260         if (!item) {
261             throw this._Lang.makeException(new Error(),null, null, this._nameSpace, "deleteItem",  "_Dom.deleteItem  Unknown Html-Component-ID: " + itemIdToReplace);
262         }
263 
264         this._removeNode(item, false);
265     },
266 
267     /**
268      * creates a node upon a given node name
269      * @param nodeName {String} the node name to be created
270      * @param attrs {Array} a set of attributes to be set
271      */
272     createElement: function(nodeName, attrs) {
273         var ret = document.createElement(nodeName);
274         if (attrs) {
275             for (var key in attrs) {
276                 if(!attrs.hasOwnProperty(key)) continue;
277                 this.setAttribute(ret, key, attrs[key]);
278             }
279         }
280         return ret;
281     },
282 
283     /**
284      * Checks whether the browser is dom compliant.
285      * Dom compliant means that it performs the basic dom operations safely
286      * without leaking and also is able to perform a native setAttribute
287      * operation without freaking out
288      *
289      *
290      * Not dom compliant browsers are all microsoft browsers in quirks mode
291      * and ie6 and ie7 to some degree in standards mode
292      * and pretty much every browser who cannot create ranges
293      * (older mobile browsers etc...)
294      *
295      * We dont do a full browser detection here because it probably is safer
296      * to test for existing features to make an assumption about the
297      * browsers capabilities
298      */
299     isDomCompliant: function() {
300         return true;
301     },
302 
303     /**
304      * proper insert before which takes tables into consideration as well as
305      * browser deficiencies
306      * @param item the node to insert before
307      * @param markup the markup to be inserted
308      */
309     insertBefore: function(item, markup) {
310         this._assertStdParams(item, markup, "insertBefore");
311 
312         markup = this._Lang.trim(markup);
313         if (markup === "") return null;
314 
315         var evalNodes = this._buildEvalNodes(item, markup),
316                 currentRef = item,
317                 parentNode = item.parentNode,
318                 ret = [];
319         for (var cnt = evalNodes.length - 1; cnt >= 0; cnt--) {
320             currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef);
321             ret.push(currentRef);
322         }
323         ret = ret.reverse();
324         this._eval(ret);
325         return ret;
326     },
327 
328     /**
329      * proper insert before which takes tables into consideration as well as
330      * browser deficiencies
331      * @param item the node to insert before
332      * @param markup the markup to be inserted
333      */
334     insertAfter: function(item, markup) {
335         this._assertStdParams(item, markup, "insertAfter");
336         markup = this._Lang.trim(markup);
337         if (markup === "") return null;
338 
339         var evalNodes = this._buildEvalNodes(item, markup),
340                 currentRef = item,
341                 parentNode = item.parentNode,
342                 ret = [];
343 
344         for (var cnt = 0; cnt < evalNodes.length; cnt++) {
345             if (currentRef.nextSibling) {
346                 //TODO winmobile6 has problems with this strategy
347                 currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef.nextSibling);
348             } else {
349                 currentRef = parentNode.appendChild(evalNodes[cnt]);
350             }
351             ret.push(currentRef);
352         }
353         this._eval(ret);
354         return ret;
355     },
356 
357 
358     /**
359      * outerHTML replacement which works cross browserlike
360      * but still is speed optimized
361      *
362      * @param item the item to be replaced
363      * @param markup the markup for the replacement
364      */
365     outerHTML : function(item, markup) {
366         this._assertStdParams(item, markup, "outerHTML");
367 
368         markup = this._Lang.trim(markup);
369         if (markup !== "") {
370             var ret = null;
371 
372             // we try to determine the browsers compatibility
373             // level to standards dom level 2 via various methods
374             if (this.isDomCompliant()) {
375                 ret = this._outerHTMLCompliant(item, markup);
376             } else {
377                 //call into abstract method
378                 ret = this._outerHTMLNonCompliant(item, markup);
379             }
380 
381             // and remove the old item
382             //first we have to save the node newly insert for easier access in our eval part
383             this._eval(ret);
384             return ret;
385         }
386         // and remove the old item, in case of an empty newtag and do nothing else
387         this._removeNode(item, false);
388         return null;
389     },
390 
391     /**
392      * detaches a set of nodes from their parent elements
393      * in a browser independend manner
394      * @param {Object} items the items which need to be detached
395      * @return {Array} an array of nodes with the detached dom nodes
396      */
397     detach: function(items) {
398         var ret = [];
399         if ('undefined' != typeof items.nodeType) {
400             if (items.parentNode) {
401                 ret.push(items.parentNode.removeChild(items));
402             } else {
403                 ret.push(items);
404             }
405             return ret;
406         }
407         //all ies treat node lists not as arrays so we have to take
408         //an intermediate step
409         var nodeArr = this._Lang.objToArray(items);
410         for (var cnt = 0; cnt < nodeArr.length; cnt++) {
411             ret.push(nodeArr[cnt].parentNode.removeChild(nodeArr[cnt]));
412         }
413         return ret;
414     },
415 
416     _outerHTMLCompliant: function(item, markup) {
417         //table element replacements like thead, tbody etc... have to be treated differently
418         var evalNodes = this._buildEvalNodes(item, markup);
419 
420         if (evalNodes.length == 1) {
421             var ret = evalNodes[0];
422             item.parentNode.replaceChild(ret, item);
423             return ret;
424         } else {
425             return this.replaceElements(item, evalNodes);
426         }
427     },
428 
429 
430 
431     /**
432      * checks if the provided element is a subelement of a table element
433      * @param item
434      */
435     _isTableElement: function(item) {
436         return !!this.TABLE_ELEMS[(item.nodeName || item.tagName).toLowerCase()];
437     },
438 
439     /**
440      * non ie browsers do not have problems with embedded scripts or any other construct
441      * we simply can use an innerHTML in a placeholder
442      *
443      * @param markup the markup to be used
444      */
445     _buildNodesCompliant: function(markup) {
446         var dummyPlaceHolder = this.getDummyPlaceHolder(); //document.createElement("div");
447         dummyPlaceHolder.innerHTML = markup;
448         return this._Lang.objToArray(dummyPlaceHolder.childNodes);
449     },
450 
451 
452 
453 
454     /**
455      * builds up a correct dom subtree
456      * if the markup is part of table nodes
457      * The usecase for this is to allow subtable rendering
458      * like single rows thead or tbody
459      *
460      * @param item
461      * @param markup
462      */
463     _buildTableNodes: function(item, markup) {
464         var itemNodeName = (item.nodeName || item.tagName).toLowerCase();
465 
466         var tmpNodeName = itemNodeName;
467         var depth = 0;
468         while (tmpNodeName != "table") {
469             item = item.parentNode;
470             tmpNodeName = (item.nodeName || item.tagName).toLowerCase();
471             depth++;
472         }
473 
474         var dummyPlaceHolder = this.getDummyPlaceHolder();
475         if (itemNodeName == "td") {
476             dummyPlaceHolder.innerHTML = "<table><tbody><tr>" + markup + "</tr></tbody></table>";
477         } else {
478             dummyPlaceHolder.innerHTML = "<table>" + markup + "</table>";
479         }
480 
481         for (var cnt = 0; cnt < depth; cnt++) {
482             dummyPlaceHolder = dummyPlaceHolder.childNodes[0];
483         }
484 
485         return this.detach(dummyPlaceHolder.childNodes);
486     },
487 
488     _removeChildNodes: function(node /*, breakEventsOpen */) {
489         if (!node) return;
490         node.innerHTML = "";
491     },
492 
493 
494 
495     _removeNode: function(node /*, breakEventsOpen*/) {
496         if (!node) return;
497         var parentNode = node.parentNode;
498         if (parentNode) //if the node has a parent
499             parentNode.removeChild(node);
500     },
501 
502 
503     /**
504      * build up the nodes from html markup in a browser independend way
505      * so that it also works with table nodes
506      *
507      * @param item the parent item upon the nodes need to be processed upon after building
508      * @param markup the markup to be built up
509      */
510     _buildEvalNodes: function(item, markup) {
511         var evalNodes = null;
512         if (this._isTableElement(item)) {
513             evalNodes = this._buildTableNodes(item, markup);
514         } else {
515             var nonIEQuirks = (!this._RT.browser.isIE || this._RT.browser.isIE > 8);
516             //ie8 has a special problem it still has the swallow scripts and other
517             //elements bug, but it is mostly dom compliant so we have to give it a special
518             //treatment
519             evalNodes = (this.isDomCompliant() &&  nonIEQuirks) ?
520                     this._buildNodesCompliant(markup) :
521                     //ie8 or quirks mode browsers
522                     this._buildNodesNonCompliant(markup);
523         }
524         return evalNodes;
525     },
526 
527     /**
528      * we have lots of methods with just an item and a markup as params
529      * this method builds an assertion for those methods to reduce code
530      *
531      * @param item  the item to be tested
532      * @param markup the mark
533      * @param caller
534      */
535     _assertStdParams: function(item, markup, caller, params) {
536         //internal error
537         if (!caller) {
538             throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "_assertStdParams",  "Caller must be set for assertion");
539         }
540         var _Lang = this._Lang,
541                 ERR_PROV = "ERR_MUST_BE_PROVIDED1",
542                 DOM = "myfaces._impl._util._Dom.",
543                 finalParams = params || ["item", "markup"];
544 
545         if (!item || !markup) {
546             _Lang.makeException(new Error(), null, null,DOM, ""+caller,  _Lang.getMessage(ERR_PROV, null, DOM +"."+ caller, (!item) ? finalParams[0] : finalParams[1]));
547             //throw Error(_Lang.getMessage(ERR_PROV, null, DOM + caller, (!item) ? params[0] : params[1]));
548         }
549     },
550 
551     /**
552      * internal eval handler used by various functions
553      * @param _nodeArr
554      */
555     _eval: function(_nodeArr) {
556         if (this.isManualScriptEval()) {
557             var isArr = _nodeArr instanceof Array;
558             if (isArr && _nodeArr.length) {
559                 for (var cnt = 0; cnt < _nodeArr.length; cnt++) {
560                     this.runScripts(_nodeArr[cnt]);
561                 }
562             } else if (!isArr) {
563                 this.runScripts(_nodeArr);
564             }
565         }
566     },
567 
568     /**
569      * for performance reasons we work with replaceElement and replaceElements here
570      * after measuring performance it has shown that passing down an array instead
571      * of a single node makes replaceElement twice as slow, however
572      * a single node case is the 95% case
573      *
574      * @param item
575      * @param evalNode
576      */
577     replaceElement: function(item, evalNode) {
578         //browsers with defect garbage collection
579         item.parentNode.insertBefore(evalNode, item);
580         this._removeNode(item, false);
581     },
582 
583 
584     /**
585      * replaces an element with another element or a set of elements
586      *
587      * @param item the item to be replaced
588      *
589      * @param evalNodes the elements
590      */
591     replaceElements: function (item, evalNodes) {
592         var evalNodesDefined = evalNodes && 'undefined' != typeof evalNodes.length;
593         if (!evalNodesDefined) {
594             throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "replaceElements",  this._Lang.getMessage("ERR_REPLACE_EL"));
595         }
596 
597         var parentNode = item.parentNode,
598 
599                 sibling = item.nextSibling,
600                 resultArr = this._Lang.objToArray(evalNodes);
601 
602         for (var cnt = 0; cnt < resultArr.length; cnt++) {
603             if (cnt == 0) {
604                 this.replaceElement(item, resultArr[cnt]);
605             } else {
606                 if (sibling) {
607                     parentNode.insertBefore(resultArr[cnt], sibling);
608                 } else {
609                     parentNode.appendChild(resultArr[cnt]);
610                 }
611             }
612         }
613         return resultArr;
614     },
615 
616     /**
617      * optimized search for an array of tag names
618      * deep scan will always be performed.
619      * @param fragment the fragment which should be searched for
620      * @param tagNames an map indx of tag names which have to be found
621      *
622      */
623     findByTagNames: function(fragment, tagNames) {
624         this._assertStdParams(fragment, tagNames, "findByTagNames", ["fragment", "tagNames"]);
625 
626         var nodeType = fragment.nodeType;
627         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
628 
629         //we can use the shortcut
630         if (fragment.querySelectorAll) {
631             var query = [];
632             for (var key in tagNames) {
633                 if(!tagNames.hasOwnProperty(key)) continue;
634                 query.push(key);
635             }
636             var res = [];
637             if (fragment.tagName && tagNames[fragment.tagName.toLowerCase()]) {
638                 res.push(fragment);
639             }
640             return res.concat(this._Lang.objToArray(fragment.querySelectorAll(query.join(", "))));
641         }
642 
643         //now the filter function checks case insensitively for the tag names needed
644         var filter = function(node) {
645             return node.tagName && tagNames[node.tagName.toLowerCase()];
646         };
647 
648         //now we run an optimized find all on it
649         try {
650             return this.findAll(fragment, filter, true);
651         } finally {
652             //the usual IE6 is broken, fix code
653             filter = null;
654         }
655     },
656 
657     /**
658      * determines the number of nodes according to their tagType
659      *
660      * @param {Node} fragment (Node or fragment) the fragment to be investigated
661      * @param {String} tagName the tag name (lowercase)
662      * (the normal usecase is false, which means if the element is found only its
663      * adjacent elements will be scanned, due to the recursive descension
664      * this should work out with elements with different nesting depths but not being
665      * parent and child to each other
666      *
667      * @return the child elements as array or null if nothing is found
668      *
669      */
670     findByTagName : function(fragment, tagName) {
671         this._assertStdParams(fragment, tagName, "findByTagName", ["fragment", "tagName"]);
672         var _Lang = this._Lang,
673                 nodeType = fragment.nodeType;
674         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
675 
676         //remapping to save a few bytes
677 
678         var ret = _Lang.objToArray(fragment.getElementsByTagName(tagName));
679         if (fragment.tagName && _Lang.equalsIgnoreCase(fragment.tagName, tagName)) ret.unshift(fragment);
680         return ret;
681     },
682 
683     findByName : function(fragment, name) {
684         this._assertStdParams(fragment, name, "findByName", ["fragment", "name"]);
685 
686         var nodeType = fragment.nodeType;
687         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
688 
689         var ret = this._Lang.objToArray(fragment.getElementsByName(name));
690         if (fragment.name == name) ret.unshift(fragment);
691         return ret;
692     },
693 
694     /**
695      * a filtered findAll for subdom treewalking
696      * (which uses browser optimizations wherever possible)
697      *
698      * @param {|Node|} rootNode the rootNode so start the scan
699      * @param filter filter closure with the syntax {boolean} filter({Node} node)
700      * @param deepScan if set to true or not set at all a deep scan is performed (for form scans it does not make much sense to deeply scan)
701      */
702     findAll : function(rootNode, filter, deepScan) {
703         this._Lang.assertType(filter, "function");
704         deepScan = !!deepScan;
705 
706         if (document.createTreeWalker && NodeFilter) {
707             return this._iteratorSearchAll(rootNode, filter, deepScan);
708         } else {
709             //will not be called in dom level3 compliant browsers
710             return this._recursionSearchAll(rootNode, filter, deepScan);
711         }
712     },
713 
714     /**
715      * the faster dom iterator based search, works on all newer browsers
716      * except ie8 which already have implemented the dom iterator functions
717      * of html 5 (which is pretty all standard compliant browsers)
718      *
719      * The advantage of this method is a faster tree iteration compared
720      * to the normal recursive tree walking.
721      *
722      * @param rootNode the root node to be iterated over
723      * @param filter the iteration filter
724      * @param deepScan if set to true a deep scan is performed
725      */
726     _iteratorSearchAll: function(rootNode, filter, deepScan) {
727         var retVal = [];
728         //Works on firefox and webkit, opera and ie have to use the slower fallback mechanis
729         //we have a tree walker in place this allows for an optimized deep scan
730         if (filter(rootNode)) {
731 
732             retVal.push(rootNode);
733             if (!deepScan) {
734                 return retVal;
735             }
736         }
737         //we use the reject mechanism to prevent a deep scan reject means any
738         //child elements will be omitted from the scan
739         var FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT,
740                 FILTER_SKIP = NodeFilter.FILTER_SKIP,
741                 FILTER_REJECT = NodeFilter.FILTER_REJECT;
742 
743         var walkerFilter = function (node) {
744             var retCode = (filter(node)) ? FILTER_ACCEPT : FILTER_SKIP;
745             retCode = (!deepScan && retCode == FILTER_ACCEPT) ? FILTER_REJECT : retCode;
746             if (retCode == FILTER_ACCEPT || retCode == FILTER_REJECT) {
747                 retVal.push(node);
748             }
749             return retCode;
750         };
751 
752         var treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, walkerFilter, false);
753         //noinspection StatementWithEmptyBodyJS
754         while (treeWalker.nextNode());
755         return retVal;
756     },
757 
758     /**
759      * bugfixing for ie6 which does not cope properly with setAttribute
760      */
761     setAttribute : function(node, attr, val) {
762         this._assertStdParams(node, attr, "setAttribute", ["fragment", "name"]);
763         if (!node.setAttribute) {
764             return;
765         }
766         node.setAttribute(attr, val);
767     },
768 
769     /**
770      * fuzzy form detection which tries to determine the form
771      * an item has been detached.
772      *
773      * The problem is some Javascript libraries simply try to
774      * detach controls by reusing the names
775      * of the detached input controls. Most of the times,
776      * the name is unique in a jsf scenario, due to the inherent form mapping.
777      * One way or the other, we will try to fix that by
778      * identifying the proper form over the name
779      *
780      * We do it in several ways, in case of no form null is returned
781      * in case of multiple forms we check all elements with a given name (which we determine
782      * out of a name or id of the detached element) and then iterate over them
783      * to find whether they are in a form or not.
784      *
785      * If only one element within a form and a given identifier found then we can pull out
786      * and move on
787      *
788      * We cannot do much further because in case of two identical named elements
789      * all checks must fail and the first elements form is served.
790      *
791      * Note, this method is only triggered in case of the issuer or an ajax request
792      * is a detached element, otherwise already existing code has served the correct form.
793      *
794      * This method was added because of
795      * https://issues.apache.org/jira/browse/MYFACES-2599
796      * to support the integration of existing ajax libraries which do heavy dom manipulation on the
797      * controls side (Dojos Dijit library for instance).
798      *
799      * @param {Node} elem - element as source, can be detached, undefined or null
800      *
801      * @return either null or a form node if it could be determined
802      *
803      * TODO move this into extended and replace it with a simpler algorithm
804      */
805     fuzzyFormDetection : function(elem) {
806         var forms = document.forms, _Lang = this._Lang;
807 
808         if (!forms || !forms.length) {
809             return null;
810         }
811 
812         // This will not work well on portlet case, because we cannot be sure
813         // the returned form is right one.
814         //we can cover that case by simply adding one of our config params
815         //the default is the weaker, but more correct portlet code
816         //you can override it with myfaces_config.no_portlet_env = true globally
817         else if (1 == forms.length && this._RT.getGlobalConfig("no_portlet_env", false)) {
818             return forms[0];
819         }
820 
821         //before going into the more complicated stuff we try the simple approach
822         var finalElem = this.byId(elem);
823         var fetchForm = _Lang.hitch(this, function(elem) {
824             //element of type form then we are already
825             //at form level for the issuing element
826             //https://issues.apache.org/jira/browse/MYFACES-2793
827 
828             return (_Lang.equalsIgnoreCase(elem.tagName, "form")) ? elem :
829                     ( this.html5FormDetection(elem) || this.getParent(elem, "form"));
830         });
831 
832         if (finalElem) {
833             var elemForm = fetchForm(finalElem);
834             if (elemForm) return elemForm;
835         }
836 
837         /**
838          * name check
839          */
840         var foundElements = [];
841         var name = (_Lang.isString(elem)) ? elem : elem.name;
842         //id detection did not work
843         if (!name) return null;
844         /**
845          * the lesser chance is the elements which have the same name
846          * (which is the more likely case in case of a brute dom replacement)
847          */
848         var nameElems = document.getElementsByName(name);
849         if (nameElems) {
850             for (var cnt = 0; cnt < nameElems.length && foundElements.length < 2; cnt++) {
851                 // we already have covered the identifier case hence we only can deal with names,
852                 var foundForm = fetchForm(nameElems[cnt]);
853                 if (foundForm) {
854                     foundElements.push(foundForm);
855                 }
856             }
857         }
858 
859         return (1 == foundElements.length ) ? foundElements[0] : null;
860     },
861 
862     html5FormDetection: function(/*item*/) {
863         return null;
864     },
865 
866 
867     /**
868      * gets a parent of an item with a given tagname
869      * @param {Node} item - child element
870      * @param {String} tagName - TagName of parent element
871      */
872     getParent : function(item, tagName) {
873 
874         if (!item) {
875             throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "getParent",
876                     this._Lang.getMessage("ERR_MUST_BE_PROVIDED1", null, "_Dom.getParent", "item {DomNode}"));
877         }
878 
879         var _Lang = this._Lang;
880         var searchClosure = function(parentItem) {
881             return parentItem && parentItem.tagName
882                     && _Lang.equalsIgnoreCase(parentItem.tagName, tagName);
883         };
884         try {
885             return this.getFilteredParent(item, searchClosure);
886         } finally {
887             searchClosure = null;
888             _Lang = null;
889         }
890     },
891 
892     /**
893      * A parent walker which uses
894      * a filter closure for filtering
895      *
896      * @param {Node} item the root item to ascend from
897      * @param {function} filter the filter closure
898      */
899     getFilteredParent : function(item, filter) {
900         this._assertStdParams(item, filter, "getFilteredParent", ["item", "filter"]);
901 
902         //search parent tag parentName
903         var parentItem = (item.parentNode) ? item.parentNode : null;
904 
905         while (parentItem && !filter(parentItem)) {
906             parentItem = parentItem.parentNode;
907         }
908         return (parentItem) ? parentItem : null;
909     },
910 
911     /**
912      * cross ported from dojo
913      * fetches an attribute from a node
914      *
915      * @param {String} node the node
916      * @param {String} attr the attribute
917      * @return the attributes value or null
918      */
919     getAttribute : function(/* HTMLElement */node, /* string */attr) {
920         return node.getAttribute(attr);
921     },
922 
923     /**
924      * checks whether the given node has an attribute attached
925      *
926      * @param {String|Object} node the node to search for
927      * @param {String} attr the attribute to search for
928      * @true if the attribute was found
929      */
930     hasAttribute : function(/* HTMLElement */node, /* string */attr) {
931         //	summary
932         //	Determines whether or not the specified node carries a value for the attribute in question.
933         return this.getAttribute(node, attr) ? true : false;	//	boolean
934     },
935 
936     /**
937      * concatenation routine which concats all childnodes of a node which
938      * contains a set of CDATA blocks to one big string
939      * @param {Node} node the node to concat its blocks for
940      */
941     concatCDATABlocks : function(/*Node*/ node) {
942         var cDataBlock = [];
943         // response may contain several blocks
944         for (var i = 0; i < node.childNodes.length; i++) {
945             cDataBlock.push(node.childNodes[i].data);
946         }
947         return cDataBlock.join('');
948     },
949 
950     //all modern browsers evaluate the scripts
951     //manually this is a w3d recommendation
952     isManualScriptEval: function() {
953         return true;
954     },
955 
956     isMultipartCandidate: function(/*executes*/) {
957         //implementation in the experimental part
958         return false;
959     },
960 
961     insertFirst: function(newNode) {
962         var body = document.body;
963         if (body.childNodes.length > 0) {
964             body.insertBefore(newNode, body.firstChild);
965         } else {
966             body.appendChild(newNode);
967         }
968     },
969 
970     byId: function(id) {
971         return this._Lang.byId(id);
972     },
973 
974     getDummyPlaceHolder: function() {
975         this._dummyPlaceHolder = this._dummyPlaceHolder ||this.createElement("div");
976         return this._dummyPlaceHolder;
977     },
978 
979     /**
980      * fetches the window id for the current request
981      * note, this is a preparation method for jsf 2.2
982      *
983      * todo move this into the experimental part
984      */
985     getWindowId: function() {
986         //implementation in the experimental part
987         return null;
988     }
989 });
990 
991 
992