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 blow up our codebase significantly
 31  */
 32 myfaces._impl.core._Runtime.singletonExtendClass("myfaces._impl._util._Dom", Object,
 33 /**
 34  * @lends myfaces._impl._util._Dom.prototype
 35  */
 36 {
 37     IE_QUIRKS_EVENTS : {
 38         "onabort": true,
 39         "onload":true,
 40         "onunload":true,
 41         "onchange": true,
 42         "onsubmit": true,
 43         "onreset": true,
 44         "onselect": true,
 45         "onblur": true,
 46         "onfocus": true,
 47         "onkeydown": true,
 48         "onkeypress": true,
 49         "onkeyup": true,
 50         "onclick": true,
 51         "ondblclick": true,
 52         "onmousedown": true,
 53         "onmousemove": true,
 54         "onmouseout": true,
 55         "onmouseover": true,
 56         "onmouseup": true
 57     },
 58 
 59     /*table elements which are used in various parts */
 60     TABLE_ELEMS:  {
 61         "thead": true,
 62         "tbody": true,
 63         "tr": true,
 64         "th": true,
 65         "td": true,
 66         "tfoot" : true
 67     },
 68 
 69     _Lang:  myfaces._impl._util._Lang,
 70     _RT:    myfaces._impl.core._Runtime,
 71     _dummyPlaceHolder:null,
 72 
 73     /**
 74      * standard constructor
 75      */
 76     constructor_: function() {
 77         //we have to trigger it upfront because mozilla runs the eval
 78         //after the dom updates and hence causes a race conditon if used on demand
 79         //under normal circumstances this works, if there are no normal ones
 80         //then this also will work at the second time, but the onload handler
 81         //should cover 99% of all use cases to avoid a loading race condition
 82         var b = myfaces._impl.core._Runtime.browser;
 83 
 84         if (b.isIE <= 6 && b.isIEMobile) {
 85             //winmobile hates add onLoad, and checks on the construct
 86             //it does not eval scripts anyway
 87             myfaces.config = myfaces.config || {};
 88             myfaces.config._autoeval = false;
 89             return;
 90         }
 91         this._RT.addOnLoad(window, function() {
 92             myfaces._impl._util._Dom.isManualScriptEval();
 93         });
 94         //safety fallback if the window onload handler is overwritten and not chained
 95         if (document.body) {
 96             this._RT.addOnLoad(document.body, function() {
 97                 myfaces._impl._util._Dom.isManualScriptEval();
 98             });
 99         }
100         //now of the onload handler also is overwritten we have a problem
101     },
102 
103     /**
104      * Run through the given Html item and execute the inline scripts
105      * (IE doesn't do this by itself)
106      * @param {Node} item
107      */
108     runScripts: function(item, xmlData) {
109         var finalScripts    = [];
110         var execScrpt       = this._Lang.hitch(this, function(item) {
111             if (item.tagName && this._Lang.equalsIgnoreCase(item.tagName, "script")) {
112                 var src = item.getAttribute('src');
113                 if ('undefined' != typeof src
114                         && null != src
115                         && src.length > 0
116                         ) {
117                     //we have to move this into an inner if because chrome otherwise chokes
118                     //due to changing the and order instead of relying on left to right
119                     if ((src.indexOf("ln=scripts") == -1 && src.indexOf("ln=javax.faces") == -1) || (src.indexOf("/jsf.js") == -1
120                             && src.indexOf("/jsf-uncompressed.js") == -1))
121                         if (finalScripts.length) {
122                             //script source means we have to eval the existing
123                             //scripts before running the include
124                             this._RT.globalEval(finalScripts.join("\n"));
125 
126                             finalScripts = [];
127                         }
128                     this._RT.loadScriptEval(src, item.getAttribute('type'), false, "UTF-8", false);
129                     //TODO handle embedded scripts
130                 } else {
131                     // embedded script auto eval
132                     var test = (!xmlData) ? item.text : this._Lang.serializeChilds(item);
133                     var go = true;
134                     while (go) {
135                         go = false;
136                         if (test.substring(0, 1) == " ") {
137                             test = test.substring(1);
138                             go = true;
139                         }
140                         if (test.substring(0, 4) == "<!--") {
141                             test = test.substring(4);
142                             go = true;
143                         }
144                         if (test.substring(0, 11) == "//<![CDATA[") {
145                             test = test.substring(11);
146                             go = true;
147                         }
148                     }
149                     // we have to run the script under a global context
150                     //we store the script for less calls to eval
151                     finalScripts.push(test);
152 
153                 }
154             }
155         });
156         try {
157             var scriptElements = this.findByTagName(item, "script", true);
158             if (scriptElements == null) return;
159             for (var cnt = 0; cnt < scriptElements.length; cnt++) {
160                 execScrpt(scriptElements[cnt]);
161             }
162             if (finalScripts.length) {
163                 this._RT.globalEval(finalScripts.join("\n"));
164             }
165         } finally {
166             //the usual ie6 fix code
167             //the IE6 garbage collector is broken
168             //nulling closures helps somewhat to reduce
169             //mem leaks, which are impossible to avoid
170             //at this browser
171             execScrpt = null;
172         }
173     },
174 
175 
176     /**
177      * determines to fetch a node
178      * from its id or name, the name case
179      * only works if the element is unique in its name
180      * @param {String} elem
181      */
182     byIdOrName: function(elem) {
183         if(!this._Lang.isString(elem)) return elem;
184         if(!elem) return null;
185         var ret = this.byId(elem);
186         if(ret) return ret;
187         //we try the unique name fallback
188         var items = document.getElementsByName(elem);
189         return ((items.length == 1)? items[0]: null);
190     },
191 
192     /**
193      * node id or name, determines the valid form identifier of a node
194      * depending on its uniqueness
195      *
196      * Usually the id is chosen for an elem, but if the id does not
197      * exist we try a name fallback. If the passed element has a unique
198      * name we can use that one as subsequent identifier.
199      *
200      *
201      * @param {String} elem
202      */
203     nodeIdOrName: function(elem) {
204         if (elem) {
205             //just to make sure that the pas
206             var origElemIdentifier = elem;
207             elem = this.byId(elem);
208             if(!elem) return null;
209             //detached element handling, we also store the element name
210             //to get a fallback option in case the identifier is not determinable
211             // anymore, in case of a framework induced detachment the element.name should
212             // be shared if the identifier is not determinable anymore
213             //the downside of this method is the element name must be unique
214             //which in case of jsf it is
215             var elementId = elem.id || elem.name;
216             if ((elem.id == null || elem.id == '') && elem.name) {
217                 elementId = elem.name;
218 
219                 //last check for uniqueness
220                 if(this.getElementsByName(elementId).length > 1) {
221                     //no unique element name so we need to perform
222                     //a return null to let the caller deal with this issue
223                     return null;
224                 }
225             }
226             return elementId;
227         }
228         return null;
229     },
230 
231     /**
232      * Simple delete on an existing item
233      */
234     deleteItem: function(itemIdToReplace) {
235         var item = this.byId(itemIdToReplace);
236         if (!item) {
237             throw Error("_Dom.deleteItem  Unknown Html-Component-ID: " + itemIdToReplace);
238         }
239 
240         this._removeNode(item, false);
241     },
242 
243 
244     /**
245      * Checks whether the browser is dom compliant.
246      * Dom compliant means that it performs the basic dom operations safely
247      * without leaking and also is able to perform a native setAttribute
248      * operation without freaking out
249      *
250      *
251      * Not dom compliant browsers are all microsoft browsers in quirks mode
252      * and ie6 and ie7 to some degree in standards mode
253      * and pretty much every browser who cannot create ranges
254      * (older mobile browsers etc...)
255      *
256      * We dont do a full browser detection here because it probably is safer
257      * to test for existing features to make an assumption about the
258      * browsers capabilities
259      */
260     isDomCompliant: function() {
261         if('undefined' == typeof this._isCompliantBrowser) {
262             this._isCompliantBrowser = !! ((window.Range
263                     && typeof Range.prototype.createContextualFragment == 'function') //createContextualFragment hints to a no quirks browser but we need more fallbacks
264                     || document.querySelectoryAll  //query selector all hints to html5 capabilities
265                     || document.createTreeWalker);   //treewalker is either firefox 3.5+ or ie9 standards mode
266         }
267         return this._isCompliantBrowser;
268     },
269 
270     /**
271      * outerHTML replacement which works cross browserlike
272      * but still is speed optimized
273      *
274      * @param item the item to be replaced
275      * @param markup the markup for the replacement
276      */
277     outerHTML : function(item, markup) {
278         if (!item) {
279             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null,"myfaces._impl._util._Dom.outerHTML", "item"));
280         }
281         if (!markup) {
282             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null,"myfaces._impl._util._Dom.outerHTML", "markup"));
283         }
284 
285         markup = this._Lang.trim(markup);
286         if (markup !== "") {
287             var ret = null;
288 
289             //w3c compliant browsers with proper contextual fragments
290             var parentNode;
291             // we try to determine the browsers compatibility
292             // level to standards dom level 2 via various methods
293             if (this.isDomCompliant()) {
294                 ret = this._outerHTMLCompliant(item, markup);
295             } else {
296 
297                 ret = this._outerHTMLNonCompliant(item, markup);
298             }
299 
300             // and remove the old item
301             //first we have to save the node newly insert for easier access in our eval part
302             if (this.isManualScriptEval()) {
303                 var isArr = ret instanceof Array;
304                 if (isArr && ret.length) {
305                     for (var cnt = 0; cnt < ret.length; cnt++) {
306                         this.runScripts(ret[cnt]);
307                     }
308                 } else if (!isArr) {
309                     this.runScripts(ret);
310                 }
311             }
312             return ret;
313         }
314         // and remove the old item, in case of an empty newtag and do nothing else
315         this._removeNode(item, false);
316         return null;
317     },
318     /**
319      * detchaes a set of nodes from their parent elements
320      * in a browser independend manner
321      * @param {Nodelist} items the items which need to be detached
322      * @return {Array} an array of nodes with the detached dom nodes
323      */
324     detach: function(items) {
325         var ret = [];
326         if('undefined' != typeof items.nodeType) {
327             if(items.parentNode) {
328                 ret.push(items.parentNode.removeChild(items));
329             } else {
330                 ret.push(items);
331             }
332             return ret;
333         }
334         //all ies treat node lists not as arrays so we have to take
335         //an intermediate step
336         var items = this._Lang.objToArray(items);
337         for(var cnt = 0; cnt < items.length; cnt++) {
338             ret.push(items[cnt].parentNode.removeChild(items[cnt]));
339         }
340         return ret;
341     },
342 
343     _outerHTMLCompliant: function(item, markup) {
344         var evalNodes;
345         //table element replacements like thead, tbody etc... have to be treated differently
346         if (this._isTableElement(item)) {
347             evalNodes = this._buildTableNodes(item, markup);
348         } else {
349             evalNodes = this._buildNodesCompliant(markup);
350         }
351         var evalNodeLen = evalNodes.length;
352 
353         if (evalNodeLen == 1) {
354             var ret = evalNodes[0];
355             item.parentNode.replaceChild(ret, item);
356             return ret;
357         } else {
358             return this.replaceElements(item, evalNodes);
359         }
360     },
361 
362     _isTable: function(item) {
363         var itemNodeName = (item.nodeName || item.tagName).toLowerCase();
364         return itemNodeName == "table";
365     },
366 
367     /**
368      * checks if the provided element is a subelement of a table element
369      * @param itemNodeName
370      */
371     _isTableElement: function(item) {
372       var itemNodeName = (item.nodeName || item.tagName).toLowerCase();
373       return !!this.TABLE_ELEMS[itemNodeName];
374     },
375 
376 
377     /**
378      * now to the evil browsers
379      * of what we are dealing with is various bugs
380      * first a simple replaceElement leaks memory
381      * secondly embedded scripts can be swallowed upon
382      * innerHTML, we probably could also use direct outerHTML
383      * but then we would run into the script swallow bug
384      *
385      * the entire mess is called IE6 and IE7
386      *
387      * @param item
388      * @param markup
389      */
390     _outerHTMLNonCompliant: function(item, markup) {
391         
392         var b = this._RT.browser;
393         var evalNodes = null;
394 
395         try {
396             //check for a subtable rendering case
397 
398             if (this._isTableElement(item)) {
399                 evalNodes = this._buildTableNodes(item, markup);
400             } else {
401                 //if no subtable element is given we simply work on our
402                 //normal markup
403                 evalNodes = this._buildNodesNonCompliant(markup);
404             }
405 
406             if (evalNodes.length == 1) {
407                 var ret = evalNodes[0];
408                 this.replaceElement(item, evalNodes[0]);
409                 return ret;
410             } else {
411                 return this.replaceElements(item, evalNodes);
412             }
413        
414         } finally {
415 
416             var dummyPlaceHolder = this.getDummyPlaceHolder();
417             //now that Microsoft has finally given
418             //ie a working gc in 8 we can skip the costly operation
419             var b = myfaces._impl.core._Runtime.browser;
420 
421             if (b.isIE && b.isIE < 8) {
422                 this._removeChildNodes(dummyPlaceHolder, false);
423             }
424             dummyPlaceHolder.innerHTML = "";
425         }
426 
427     },
428 
429     /**
430      * non ie browsers do not have problems with embedded scripts or any other construct
431      * we simply can use an innerHTML in a placeholder
432      *
433      * @param markup the markup to be used
434      */
435     _buildNodesCompliant: function(markup) {
436         var dummyPlaceHolder = this.getDummyPlaceHolder(); //document.createElement("div");
437         dummyPlaceHolder.innerHTML = markup;
438         return this._Lang.objToArray(dummyPlaceHolder.childNodes);
439     },
440 
441 
442 
443 
444     /**
445      * builds up a correct dom subtree
446      * if the markup is part of table nodes
447      * The usecase for this is to allow subtable rendering
448      * like single rows thead or tbody
449      *
450      * @param item
451      * @param markup
452      */
453     _buildTableNodes: function(item, markup) {
454         var evalNodes;
455         var itemNodeName = (item.nodeName || item.tagName).toLowerCase();
456         var probe = this.getDummyPlaceHolder(); //document.createElement("div");
457         if (itemNodeName == "td") {
458             probe.innerHTML = "<table><tbody><tr><td></td></tr></tbody></table>";
459         } else {
460             probe.innerHTML = "<table><" + itemNodeName + "></" + itemNodeName + ">" + "</table>";
461         }
462         var depth = this._determineDepth(probe);
463         
464         this._removeChildNodes(probe, false);
465         probe.innerHTML = "";
466 
467         var dummyPlaceHolder = this.getDummyPlaceHolder();//document.createElement("div");
468         if (itemNodeName == "td") {
469             dummyPlaceHolder.innerHTML = "<table><tbody><tr>" + markup + "</tr></tbody></table>";
470         } else {
471             dummyPlaceHolder.innerHTML = "<table>" + markup + "</table>";
472         }
473         evalNodes = dummyPlaceHolder;
474         for (var cnt = 0; cnt < depth; cnt++) {
475             evalNodes = evalNodes.childNodes[0];
476         }
477         evalNodes = (evalNodes.parentNode) ? evalNodes.parentNode.childNodes : null;
478         return this.detach(evalNodes);
479     }
480     ,
481 
482     /**
483      * builds the ie nodes properly in a placeholder
484      * and bypasses a non script insert bug that way
485      * @param markup the marku code
486      */
487     _buildNodesNonCompliant: function(markup) {
488 
489         var evalNodes = null;
490 
491         //now to the non w3c compliant browsers
492         //http://blogs.perl.org/users/clinton_gormley/2010/02/forcing-ie-to-accept-script-tags-in-innerhtml.html
493         //we have to cope with deficiencies between ie and its simulations in this case
494         var probe = this.getDummyPlaceHolder();//document.createElement("div");
495 
496         probe.innerHTML = "<table><tbody><tr><td><div></div></td></tr></tbody></table>";
497 
498         //we have customers using html unit, this has a bug in the table resolution
499         //hence we determine the depth dynamically
500         var depth = this._determineDepth(probe);
501         var newProbe = probe;
502         this._removeChildNodes(probe, false);
503         probe.innerHTML = "";
504 
505         var dummyPlaceHolder = this.getDummyPlaceHolder();//document.createElement("div");
506 
507         //fortunately a table element also works which is less critical than form elements regarding
508         //the inner content
509         dummyPlaceHolder.innerHTML = "<table><tbody><tr><td>" + markup + "</td></tr></tbody></table>";
510         evalNodes = dummyPlaceHolder;
511 
512         for (var cnt = 0; cnt < depth; cnt++) {
513             evalNodes = evalNodes.childNodes[0];
514         }
515         var ret = (evalNodes.parentNode) ? this.detach(evalNodes.parentNode.childNodes) : null;
516 
517         if ('undefined' == typeof evalNodes || null == evalNodes) {
518             //fallback for htmlunit which should be good enough
519             //to run the tests, maybe we have to wrap it as well
520             dummyPlaceHolder.innerHTML = "<div>" + markup + "</div>";
521             //note this is triggered only in htmlunit no other browser
522             //so we are save here
523             evalNodes = this.detach(dummyPlaceHolder.childNodes[0].childNodes);
524         }
525 
526         this._removeChildNodes(dummyPlaceHolder, false);
527         //ie fix any version, ie does not return true javascript arrays so we have to perform
528         //a cross conversion
529         return ret;
530 
531     },
532 
533     _determineDepth: function(probe) {
534         var depth = 0;
535         var newProbe = probe;
536         for (;newProbe &&
537                 newProbe.childNodes &&
538                 newProbe.childNodes.length &&
539                 newProbe.nodeType == 1; depth++) {
540             newProbe = newProbe.childNodes[0];
541         }
542         return depth;
543     },
544 
545 
546     //now to another nasty issue:
547     //for ie we have to walk recursively over all nodes:
548     //http://msdn.microsoft.com/en-us/library/bb250448%28VS.85%29.aspx
549     //http://weblogs.java.net/blog/driscoll/archive/2009/11/13/ie-memory-management-and-you
550     //http://home.orange.nl/jsrosman/
551     //http://www.quirksmode.org/blog/archives/2005/10/memory_leaks_li.html
552     //http://www.josh-davis.org/node/7
553     _removeNode: function(node, breakEventsOpen) {
554         if (!node) return;
555         var b = this._RT.browser;
556         if (this.isDomCompliant()) {
557             //recursive descension only needed for old ie versions
558             //all newer browsers cleanup the garbage just fine without it
559             //thank you
560             if ('undefined' != typeof node.parentNode && null != node.parentNode) //if the node has a parent
561                 node.parentNode.removeChild(node);
562             return;
563         }
564 
565         //now to the browsers with non working garbage collection
566         this._removeChildNodes(node, breakEventsOpen);
567 
568         try {
569             //outer HTML setting is only possible in earlier IE versions all modern browsers throw an exception here
570             //again to speed things up we precheck first
571             if(!this._isTableElement(childNode)) {
572                 //we do not do a table structure innnerhtml on table elements except td
573                 //htmlunit rightfully complains that we should not do it
574                 node.innerHTML = "";
575             }
576             if (b.isIE && 'undefined' != typeof node.outerHTML) {//ie8+ check done earlier we skip it here
577                 node.outerHTML = '';
578             } else {
579                 node = this.detach(node)[0];
580             }
581             if (!b.isIEMobile) {
582                 delete node;
583             }
584         } catch (e) {
585             //on some elements we might not have covered by our table check on the outerHTML
586             // can fail we skip those in favor of stability
587             try {
588                 // both innerHTML and outerHTML fails when <tr> is the node, but in that case 
589                 // we need to force node removal, otherwise it will be on the tree (IE 7 IE 6)
590                 this.detach(node);
591             } catch (e1) {
592             }
593         }
594     }
595     ,
596 
597 
598 
599     /**
600      * recursive delete child nodes
601      * node, this method only makes sense in the context of IE6 + 7 hence
602      * it is not exposed to the public API, modern browsers
603      * can garbage collect the nodes just fine by doing the standard removeNode method
604      * from the dom API!
605      *
606      * @param node  the node from which the childnodes have to be deletd
607      * @param breakEventsOpen if set to true a standard events breaking is performed
608      */
609     _removeChildNodes: function(node, breakEventsOpen) {
610         if (!node) return;
611 
612         //node types which cannot be cleared up by normal means
613         var disallowedNodes = this.TABLE_ELEMS;
614 
615         //for now we do not enable it due to speed reasons
616         //normally the framework has to do some event detection
617         //which we cannot do yet, I will dig for options
618         //to enable it in a speedly manner
619         //ie7 fixes this area anyway
620         //this.breakEvents(node);
621 
622         var b = this._RT.browser;
623         if (breakEventsOpen) {
624             this.breakEvents(node);
625         }
626 
627         for (var cnt = node.childNodes.length - 1; cnt >= 0; cnt -= 1) {
628             var childNode = node.childNodes[cnt];
629             //we cannot use our generic recursive tree walking due to the needed head recursion
630             //to clean it up bottom up, the tail recursion we were using in the search either would use more time
631             //because we had to walk and then clean bottom up, so we are going for a direct head recusion here
632             if ('undefined' != typeof childNode.childNodes && node.childNodes.length)
633                 this._removeChildNodes(childNode);
634             try {
635                 var nodeName = (childNode.nodeName || childNode.tagName) ? (childNode.nodeName || childNode.tagName).toLowerCase() : null;
636                 //ie chokes on clearing out table inner elements, this is also covered by our empty
637                 //catch block, but to speed things up it makes more sense to precheck that
638                 if (!disallowedNodes[nodeName]) {
639                     //outer HTML setting is only possible in earlier IE versions all modern browsers throw an exception here
640                     //again to speed things up we precheck first
641                     if(!this._isTableElement(childNode)) {    //table elements cannot be deleted
642                         childNode.innerHTML = "";
643                     }
644                     if (b.isIE && b.isIE < 8 && 'undefined' != childNode.outerHTML)
645                         childNode.outerHTML = '';
646                     else {
647                         node.removeChild(childNode);
648                     }
649                     if (!b.isIEMobile) {
650                         delete childNode;
651                     }
652                 }
653             } catch (e) {
654                 //on some elements the outerHTML can fail we skip those in favor
655                 //of stability
656 
657             }
658         }
659     }
660     ,
661 
662     /**
663      * break the standard events from an existing dom node
664      * (note this method is not yet used, but can be used
665      * by framework authors to get rid of ie circular event references)
666      *
667      * another way probably would be to check all attributes of a node
668      * for a function and if one is present break it by nulling it
669      * I have to do some further investigation on this.
670      *
671      * The final fix is to move away from ie6 at all which is the root cause of
672      * this.
673      *
674      * @param node the node which has to be broken off its events
675      */
676     breakEvents: function(node) {
677         if (!node) return;
678         var evtArr = this.IE_QUIRKS_EVENTS;
679         for (var key in evtArr) {
680             if (key != "onunload" && node[key]) {
681                 node[key] = null;
682             }
683         }
684     }
685     ,
686 
687 
688     /**
689      * for performance reasons we work with replaceElement and replaceElements here
690      * after measuring performance it has shown that passing down an array instead
691      * of a single node makes replaceElement twice as slow, however
692      * a single node case is the 95% case
693      *
694      * @param item
695      * @param evalNodes
696      */
697     replaceElement: function(item, evalNode) {
698 
699         var _Browser = this._RT.browser;
700         if (!_Browser.isIE || _Browser.isIE >= 8) {
701             //standards conform no leaking browser
702             item.parentNode.replaceChild(evalNode, item);
703         } else {
704             //browsers with defect garbage collection
705             item.parentNode.insertBefore(evalNode, item);
706             this._removeNode(item, false);
707         }
708     }
709     ,
710 
711 
712     /**
713      * replaces an element with another element or a set of elements
714      *
715      * @param item the item to be replaced
716      *
717      * @param evalNodes the elements
718      */
719     replaceElements: function (item, evalNodes) {
720         var evalNodesDefined = evalNodes && 'undefined' != typeof evalNodes.length;
721         if (!evalNodesDefined) {
722             throw new Error(this._Lang.getMessage("ERR_REPLACE_EL"));
723         }
724 
725         var parentNode = item.parentNode;
726 
727         var sibling = item.nextSibling;
728         var resultArr = this._Lang.objToArray(evalNodes);
729 
730         for (var cnt = 0; cnt < resultArr.length; cnt++) {
731             if (cnt == 0) {
732                 this.replaceElement(item, resultArr[cnt]);
733             } else {
734                 if (sibling) {
735                     parentNode.insertBefore(resultArr[cnt], sibling);
736                 } else {
737                     parentNode.appendChild(resultArr[cnt]);
738 
739                 }
740             }
741         }
742 
743         return resultArr;
744     }
745     ,
746 
747     /**
748      * optimized search for an array of tag names
749      *
750      * @param fragment the fragment which should be searched for
751      * @param tagNames an map indx of tag names which have to be found
752      * @param deepScan if set to true a deep scan is performed otherwise a shallow scan
753      */
754     findByTagNames: function(fragment, tagNames, deepScan) {
755         var nodeType = fragment.nodeType;
756         if(nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
757 
758 
759         //shortcut for single components
760         if (!deepScan && tagNames[fragment.tagName.toLowerCase()]) {
761             return fragment;
762         }
763 
764         //shortcut elementsByTagName
765         if (deepScan && this._Lang.exists(fragment, "getElementsByTagName")) {
766             var retArr = [];
767             for (var key in tagNames) {
768                 var foundElems = this.findByTagName(fragment, key, deepScan);
769                 if (foundElems) {
770                     retArr = retArr.concat(foundElems);
771                 }
772             }
773             return retArr;
774         } else if (deepScan) {
775             //no node type with child tags we can handle that without node type checking
776             return null;
777         }
778 
779         //now the filter function checks case insensitively for the tag names needed
780         var filter = function(node) {
781             return node.tagName && tagNames[node.tagName.toLowerCase()];
782         };
783 
784         //now we run an optimized find all on it
785         try {
786             return this.findAll(fragment, filter, deepScan);
787         } finally {
788             //the usual IE6 is broken, fix code
789             filter = null;
790         }
791     }
792     ,
793 
794     /**
795      * determines the number of nodes according to their tagType
796      *
797      * @param {Node} fragment (Node or fragment) the fragment to be investigated
798      * @param {String} tagName the tag name (lowercase)
799      * @param {Boolean} deepScan if set to true a found element does not prevent to scan deeper
800      * (the normal usecase is false, which means if the element is found only its
801      * adjacent elements will be scanned, due to the recursive descension
802      * this should work out with elements with different nesting depths but not being
803      * parent and child to each other
804      *
805      * @return the child elements as array or null if nothing is found
806      *
807      */
808     findByTagName : function(fragment, tagName, deepScan) {
809         var nodeType = fragment.nodeType;
810         if(nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
811 
812 
813         //remapping to save a few bytes
814         var _Lang = this._Lang;
815 
816         deepScan = !!deepScan;
817 
818         //elements by tagname is the fastest, ie throws an error on fragment.getElementsByTagName, the exists type
819         //via namespace array checking is safe
820         if (deepScan && _Lang.exists(fragment, "getElementsByTagName")) {
821             var ret = _Lang.objToArray(fragment.getElementsByTagName(tagName));
822             if (fragment.tagName && _Lang.equalsIgnoreCase(fragment.tagName, tagName)) ret.unshift(fragment);
823             return ret;
824         } else if (deepScan) {
825             //no node type with child tags we can handle that without node type checking
826             return null;
827         }
828         //since getElementsByTagName is a standardized dom node function and ie also supports
829         //it since 5.5
830         //we need no fallback to the query api and the recursive filter
831         //also is only needed in case of no deep scan or non dom elements
832 
833         var filter = function(node) {
834             return node.tagName && _Lang.equalsIgnoreCase(node.tagName, tagName);
835         };
836         try {
837             return this.findAll(fragment, filter, deepScan);
838         } finally {
839             //the usual IE6 is broken, fix code
840             filter = null;
841             _Lang = null;
842         }
843 
844     }
845     ,
846 
847     findByName : function(fragment, name, deepScan) {
848         var nodeType = fragment.nodeType;
849         if(nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
850 
851         var _Lang = this._Lang;
852         var filter = function(node) {
853             return  node.name && _Lang.equalsIgnoreCase(node.name, name);
854         };
855         try {
856             deepScan = !!deepScan;
857 
858             //elements byName is the fastest
859             if (deepScan && _Lang.exists(fragment, "getElementsByName")) {
860                 var ret = _Lang.objToArray(fragment.getElementsByName(name));
861                 if (fragment.name == name) ret.unshift(fragment);
862                 return ret;
863             }
864 
865             if (deepScan && _Lang.exists(fragment, "querySelectorAll")) {
866                 try {
867                     var newName = name;
868                     if (_Lang.isString(newName)) {
869                         newName = _Lang.escapeString(newName);
870                     }
871                     var result = fragment.querySelectorAll("[name=" + newName + "]");
872                     if (fragment.nodeType == 1 && filter(fragment)) {
873                         result = (result == null) ? [] : _Lang.objToArray(result);
874                         result.push(fragment);
875                     }
876                     return result;
877                 } catch(e) {
878                     //in case the selector bombs we retry manually
879                 }
880             }
881 
882             return this.findAll(fragment, filter, deepScan);
883         } finally {
884             //the usual IE6 is broken, fix code
885             filter = null;
886             _Lang = null;
887         }
888     }
889     ,
890 
891 
892 
893     /**
894      * a filtered findAll for subdom treewalking
895      * (which uses browser optimizations wherever possible)
896      *
897      * @param {|Node|} rootNode the rootNode so start the scan
898      * @param filter filter closure with the syntax {boolean} filter({Node} node)
899      * @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)
900      */
901     findAll : function(rootNode, filter, deepScan) {
902         this._Lang.assertType(filter, "function");
903         deepScan = !!deepScan;
904 
905         if (document.createTreeWalker && NodeFilter) {
906             return this._iteratorSearchAll(rootNode, filter, deepScan);
907         } else {
908             return this._recursionSearchAll(rootNode, filter, deepScan);
909         }
910 
911     }
912     ,
913 
914     /**
915      * classical recursive way which definitely will work on all browsers
916      * including the IE6
917      *
918      * @param rootNode the root node
919      * @param filter the filter to be applied to
920      * @param deepScan if set to true a deep scan is performed
921      */
922     _recursionSearchAll: function(rootNode, filter, deepScan) {
923         var ret = [];
924         //fix the value to prevent undefined errors
925 
926         if (filter(rootNode)) {
927             ret.push(rootNode);
928             if (!deepScan) return ret;
929         }
930 
931         //
932         if (!rootNode.childNodes) {
933             return ret;
934         }
935 
936         //subfragment usecases
937 
938         var retLen = ret.length;
939         var childLen = rootNode.childNodes.length;
940         for (var cnt = 0; (deepScan || retLen == 0) && cnt < childLen; cnt++) {
941             ret = ret.concat(this._recursionSearchAll(rootNode.childNodes[cnt], filter, deepScan));
942         }
943         return ret;
944     }
945     ,
946 
947     /**
948      * the faster dom iterator based search, works on all newer browsers
949      * except ie8 which already have implemented the dom iterator functions
950      * of html 5 (which is pretty all standard compliant browsers)
951      *
952      * The advantage of this method is a faster tree iteration compared
953      * to the normal recursive tree walking.
954      *
955      * @param rootNode the root node to be iterated over
956      * @param filter the iteration filter
957      * @param deepScan if set to true a deep scan is performed
958      */
959     _iteratorSearchAll: function(rootNode, filter, deepScan) {
960         var retVal = [];
961         //Works on firefox and webkit, opera and ie have to use the slower fallback mechanis
962         //we have a tree walker in place this allows for an optimized deep scan
963         if (filter(rootNode)) {
964 
965             retVal.push(rootNode);
966             if (!deepScan) {
967                 return retVal;
968             }
969         }
970         //we use the reject mechanism to prevent a deep scan reject means any
971         //child elements will be omitted from the scan
972         var walkerFilter = function (node) {
973             var retCode = (filter(node)) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
974             retCode = (!deepScan && retCode == NodeFilter.FILTER_ACCEPT) ? NodeFilter.FILTER_REJECT : retCode;
975             if (retCode == NodeFilter.FILTER_ACCEPT || retCode == NodeFilter.FILTER_REJECT) {
976                 retVal.push(node);
977             }
978             return retCode;
979         };
980         var treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, walkerFilter, false);
981         //noinspection StatementWithEmptyBodyJS
982         while (treeWalker.nextNode());
983         return retVal;
984     }
985     ,
986 
987     /**
988      * bugfixing for ie6 which does not cope properly with setAttribute
989      */
990     setAttribute : function(node, attr, val) {
991 
992         if (!node) {
993             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.setAttribute", "node {DomNode}"));
994         }
995         if (!attr) {
996             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.setAttribute", "attr {String}"));
997         }
998 
999         //quirks mode and ie7 mode has the attributes problems ie8 standards mode behaves like
1000         //a good citizen
1001         var _Browser = this._RT.browser;
1002         //in case of ie > ie7 we have to check for a quirks mode setting
1003         if (!_Browser.isIE || _Browser.isIE > 7 && this.isDomCompliant()) {
1004             if (!node.setAttribute) {
1005                 return;
1006             }
1007             node.setAttribute(attr, val);
1008             return;
1009         }
1010 
1011         /*
1012          Now to the broken browsers IE6+.... ie7 and ie8 quirks mode
1013 
1014          we deal mainly with three problems here
1015          class and for are not handled correctly
1016          styles are arrays and cannot be set directly
1017          and javascript events cannot be set via setAttribute as well!
1018 
1019          or in original words of quirksmode.org ... this is a mess!
1020 
1021          Btw. thank you Microsoft for providing all necessary tools for free
1022          for being able to debug this entire mess in the ie rendering engine out
1023          (which is the Microsoft ie vms, developers toolbar, Visual Web Developer 2008 express
1024          and the ie8 8 developers toolset!)
1025 
1026          also thank you http://www.quirksmode.org/
1027          dojotoolkit.org and   //http://delete.me.uk/2004/09/ieproto.html
1028          for additional information on this mess!
1029 
1030          The lowest common denominator tested within this code
1031          is IE6, older browsers for now are legacy!
1032          */
1033         attr = attr.toLowerCase();
1034 
1035         if (attr === "class") {
1036             //setAttribute does not work for winmobile browsers
1037             //firect calls work
1038             node.className = val;
1039         } else if (attr === "name") {
1040             //the ie debugger fails to assign the name via setAttr
1041             //in quirks mode
1042             node[attr] = val;
1043         } else if (attr === "for") {
1044             if (!_Browser.isIEMobile || _Browser.isIEMobile >= 7) {
1045                 node.setAttribute("htmlFor", val);
1046             } else {
1047                 node.htmlFor = val;
1048             }
1049         } else if (attr === "style") {
1050             //We have to split the styles here and assign them one by one
1051             var styles = val.split(";");
1052             var stylesLen = styles.length;
1053             for (var loop = 0; loop < stylesLen; loop++) {
1054                 var keyVal = styles[loop].split(":");
1055                 if (keyVal[0] != "" && keyVal[0] == "opacity") {
1056                     //special ie quirks handling for opacity
1057 
1058                     var opacityVal = Math.max(100, Math.round(parseFloat(keyVal[1]) * 10));
1059                     //probably does not work in ie mobile anyway
1060                     if (!_Browser.isIEMobile || _Browser.isIEMobile >= 7) {
1061                         node.style.setAttribute("arrFilter", "alpha(opacity=" + opacityVal + ")");
1062                     }
1063                     //if you need more hacks I would recommend
1064                     //to use the class attribute and conditional ie includes!
1065                 } else if (keyVal[0] != "") {
1066                     if (!_Browser.isIEMobile || _Browser.isIEMobile >= 7) {
1067                         node.style.setAttribute(keyVal[0], keyVal[1]);
1068                     } else {
1069                         node.style[keyVal[0]] = keyVal[1];
1070                     }
1071                 }
1072             }
1073         } else {
1074             //check if the attribute is an event, since this applies only
1075             //to quirks mode of ie anyway we can live with the standard html4/xhtml
1076             //ie supported events
1077             if (this.IE_QUIRKS_EVENTS[attr]) {
1078                 if (this._Lang.isString(attr)) {
1079                     //event resolves to window.event in ie
1080                     node.setAttribute(attr, function() {
1081                         //event implicitly used
1082                         return this._Lang.globalEval(val);
1083                     });
1084                 }
1085             } else {
1086                 //unknown cases we try to catch them via standard setAttributes
1087                 if (!_Browser.isIEMobile || _Browser.isIEMobile >= 7) {
1088                     node.setAttribute(attr, val);
1089                 } else {
1090                     node[attr] = val;
1091                 }
1092             }
1093         }
1094     }
1095     ,
1096 
1097 
1098     /**
1099      * fuzzy form detection which tries to determine the form
1100      * an item has been detached.
1101      *
1102      * The problem is some Javascript libraries simply try to
1103      * detach controls by reusing the names
1104      * of the detached input controls. Most of the times,
1105      * the name is unique in a jsf scenario, due to the inherent form mapping.
1106      * One way or the other, we will try to fix that by
1107      * identifying the proper form over the name
1108      *
1109      * We do it in several ways, in case of no form null is returned
1110      * in case of multiple forms we check all elements with a given name (which we determine
1111      * out of a name or id of the detached element) and then iterate over them
1112      * to find whether they are in a form or not.
1113      *
1114      * If only one element within a form and a given identifier found then we can pull out
1115      * and move on
1116      *
1117      * We cannot do much further because in case of two identical named elements
1118      * all checks must fail and the first elements form is served.
1119      *
1120      * Note, this method is only triggered in case of the issuer or an ajax request
1121      * is a detached element, otherwise already existing code has served the correct form.
1122      *
1123      * This method was added because of
1124      * https://issues.apache.org/jira/browse/MYFACES-2599
1125      * to support the integration of existing ajax libraries which do heavy dom manipulation on the
1126      * controls side (Dojos Dijit library for instance).
1127      *
1128      * @param {Node} elem - element as source, can be detached, undefined or null
1129      *
1130      * @return either null or a form node if it could be determined
1131      */
1132     fuzzyFormDetection : function(elem) {
1133         if (!document.forms || !document.forms.length) {
1134             return null;
1135         }
1136 
1137         // This will not work well on portlet case, because we cannot be sure
1138         // the returned form is right one.
1139         //we can cover that case by simply adding one of our config params
1140         //the default is the weaker, but more correct portlet code
1141         //you can override it with myfaces_config.no_portlet_env = true globally
1142         else if (1 == document.forms.length && this._RT.getGlobalConfig("no_portlet_env", false)) {
1143             return document.forms[0];
1144         }
1145         if (!elem) {
1146             return null;
1147         }
1148 
1149         //before going into the more complicated stuff we try the simple approach
1150         if (!this._Lang.isString(elem)) {
1151 
1152             //html 5 allows finally the detachement of elements
1153             //by introducing a form attribute
1154 
1155             var elemForm = this.html5FormDetection(elem);
1156             if (elemForm) {
1157                 return elemForm;
1158             }
1159 
1160             //element of type form then we are already
1161             //at form level for the issuing element
1162             //https://issues.apache.org/jira/browse/MYFACES-2793
1163 
1164             if (this._Lang.equalsIgnoreCase(elem.tagName, "form")) {
1165                 return elem;
1166             }
1167             var ret = this.getParent(elem, "form");
1168             if (ret) return ret;
1169         } else {
1170             elem = this.byId(elem);
1171             // element might have removed from DOM in method processUpdate 
1172             if (!elem){
1173             	return null;
1174             }
1175             var ret = this.getParent(elem, "form");
1176             if (ret) return ret;
1177         }
1178 
1179         var id = elem.id || null;
1180         var name = elem.name || null;
1181         //a framework in a detachment case also can replace an existing identifier element
1182         // with a name element
1183         name = name || id;
1184         var foundForm;
1185 
1186         if (id && '' != id) {
1187             //we have to assert that the element passed down is detached
1188             var domElement = this.byId(id);
1189             var elemForm = this.html5FormDetection(domElement);
1190             if (elemForm) {
1191                 return elemForm;
1192             }
1193 
1194             if (domElement) {
1195                 foundForm = this.getParent(domElement, "form");
1196                 if (null != foundForm) return foundForm;
1197             }
1198         }
1199 
1200         /**
1201          * name check
1202          */
1203         var foundElements = [];
1204 
1205         /**
1206          * the lesser chance is the elements which have the same name
1207          * (which is the more likely case in case of a brute dom replacement)
1208          */
1209         var nameElems = document.getElementsByName(name);
1210         if (nameElems) {
1211             for (var cnt = 0; cnt < nameElems.length && foundElements.length < 2; cnt++) {
1212                 // we already have covered the identifier case hence we only can deal with names,
1213                 foundForm = this.getParent(nameElems[cnt], "form");
1214                 if (null != foundForm) {
1215                     foundElements.push(foundForm);
1216                 }
1217             }
1218         }
1219 
1220         return (1 == foundElements.length ) ? foundElements[0] : null;
1221     }
1222     ,
1223 
1224     html5FormDetection: function(item) {
1225         if (this._RT.browser.isIEMobile && this._RT.browser.isIEMobile <= 7) {
1226             return null;
1227         }
1228         var elemForm = this.getAttribute(item, "form");
1229         if (elemForm) {
1230             return this.byId(elemForm);
1231         }
1232         return null;
1233     }
1234     ,
1235 
1236     /**
1237      * gets a parent of an item with a given tagname
1238      * @param {Node} item - child element
1239      * @param {String} tagName - TagName of parent element
1240      */
1241     getParent : function(item, tagName) {
1242 
1243         if (!item) {
1244             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.getParent", "item {DomNode}"));
1245         }
1246 
1247         var _Lang = this._Lang;
1248         var searchClosure = function(parentItem) {
1249             return parentItem && parentItem.tagName
1250                     && _Lang.equalsIgnoreCase(parentItem.tagName, tagName);
1251         };
1252         try {
1253             return this.getFilteredParent(item, searchClosure);
1254         } finally {
1255             searchClosure = null;
1256             _Lang = null;
1257         }
1258     }
1259     ,
1260 
1261     /**
1262      * A parent walker which uses
1263      * a filter closure for filtering
1264      *
1265      * @param {Node} item the root item to ascend from
1266      * @param {function} filter the filter closure
1267      */
1268     getFilteredParent : function(item, filter) {
1269         if (!item) {
1270             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.getFilteredParent", "item {DomNode}"));
1271         }
1272         if (!filter) {
1273             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.getFilteredParent", "filter {function}"));
1274         }
1275 
1276         //search parent tag parentName
1277         var parentItem = (item.parentNode) ? item.parentNode : null;
1278 
1279         while (parentItem && !filter(parentItem)) {
1280             parentItem = parentItem.parentNode;
1281         }
1282         return (parentItem) ? parentItem : null;
1283     }
1284     ,
1285 
1286     /**
1287      * a closure based child filtering routine
1288      * which steps one level down the tree and
1289      * applies the filter closure
1290      *
1291      * @param item the node which has to be investigates
1292      * @param filter the filter closure
1293      */
1294     getFilteredChild: function(item, filter) {
1295         if (!item) {
1296             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.getFilteredParent", "item {DomNode}"));
1297         }
1298         if (!filter) {
1299             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1",null, "_Dom.getFilteredParent", "filter {function}"));
1300         }
1301 
1302         var childs = item.childNodes;
1303         if (!childs) {
1304             return null;
1305         }
1306         for (var c = 0, cLen = childs.length; c < cLen; c++) {
1307             if (filter(childs[c])) {
1308                 return childs[c];
1309             }
1310         }
1311         return null;
1312     }
1313     ,
1314 
1315     /**
1316      * gets the child of an item with a given tag name
1317      * @param {Node} item - parent element
1318      * @param {String} childName - TagName of child element
1319      * @param {String} itemName - name  attribute the child can have (can be null)
1320      * @Deprecated
1321      */
1322     getChild: function(item, childName, itemName) {
1323         var _Lang = this._Lang;
1324 
1325         function filter(node) {
1326             return node.tagName
1327                     && _Lang.equalsIgnoreCase(node.tagName, childName)
1328                     && (!itemName || (itemName && itemName == node.getAttribute("name")));
1329 
1330         }
1331 
1332         return this.getFilteredChild(item, filter);
1333     }
1334     ,
1335 
1336     /**
1337      * cross ported from dojo
1338      * fetches an attribute from a node
1339      *
1340      * @param {String} node the node
1341      * @param {String} attr the attribute
1342      * @return the attributes value or null
1343      */
1344     getAttribute : function(/* HTMLElement */node, /* string */attr) {
1345         //	summary
1346         //	Returns the value of attribute attr from node.
1347         node = this.byId(node);
1348         // FIXME: need to add support for attr-specific accessors
1349         if ((!node) || (!node.getAttribute)) {
1350             // if(attr !== 'nwType'){
1351             //	alert("getAttr of '" + attr + "' with bad node");
1352             // }
1353             return null;
1354         }
1355         var ta = typeof attr == 'string' ? attr : new String(attr);
1356 
1357         // first try the approach most likely to succeed
1358         var v = node.getAttribute(ta.toUpperCase());
1359         if ((v) && (typeof v == 'string') && (v != "")) {
1360             return v;	//	string
1361         }
1362 
1363         // try returning the attributes value, if we couldn't get it as a string
1364         if (v && v.value) {
1365             return v.value;	//	string
1366         }
1367 
1368         // this should work on Opera 7, but it's a little on the crashy side
1369         if ((node.getAttributeNode) && (node.getAttributeNode(ta))) {
1370             return (node.getAttributeNode(ta)).value;	//	string
1371         } else if (node.getAttribute(ta)) {
1372             return node.getAttribute(ta);	//	string
1373         } else if (node.getAttribute(ta.toLowerCase())) {
1374             return node.getAttribute(ta.toLowerCase());	//	string
1375         }
1376         return null;	//	string
1377     }
1378     ,
1379 
1380     /**
1381      * checks whether the given node has an attribute attached
1382      *
1383      * @param {String|Object} node the node to search for
1384      * @param {String} attr the attribute to search for
1385      * @true if the attribute was found
1386      */
1387     hasAttribute : function(/* HTMLElement */node, /* string */attr) {
1388         //	summary
1389         //	Determines whether or not the specified node carries a value for the attribute in question.
1390         return this.getAttribute(node, attr) ? true : false;	//	boolean
1391     }
1392     ,
1393 
1394     /**
1395      * fetches the style class for the node
1396      * cross ported from the dojo toolkit
1397      * @param {String|Object} node the node to search
1398      * @returns the className or ""
1399      */
1400     getClass : function(node) {
1401         node = this.byId(node);
1402         if (!node) {
1403             return "";
1404         }
1405         var cs = "";
1406         if (node.className) {
1407             cs = node.className;
1408         } else {
1409             if (this.hasAttribute(node, "class")) {
1410                 cs = this.getAttribute(node, "class");
1411             }
1412         }
1413         return cs.replace(/^\s+|\s+$/g, "");
1414     }
1415     ,
1416     /**
1417      * fetches the class for the node,
1418      * cross ported from the dojo toolkit
1419      * @param {String|Object}node the node to search
1420      */
1421     getClasses : function(node) {
1422         var c = this.getClass(node);
1423         return (c == "") ? [] : c.split(/\s+/g);
1424     }
1425     ,
1426 
1427     /**
1428      * concatenation routine which concats all childnodes of a node which
1429      * contains a set of CDATA blocks to one big string
1430      * @param {Node} node the node to concat its blocks for
1431      */
1432     concatCDATABlocks : function(/*Node*/ node) {
1433         var cDataBlock = [];
1434         // response may contain several blocks
1435         for (var i = 0; i < node.childNodes.length; i++) {
1436             cDataBlock.push(node.childNodes[i].data);
1437         }
1438         return cDataBlock.join('');
1439     }
1440     ,
1441 
1442     isManualScriptEval: function() {
1443 
1444         if (!this._Lang.exists(myfaces, "config._autoeval")) {
1445             var _Browser = this._RT.browser;
1446             //now we rely on the document being processed if called for the first time
1447             var evalDiv = document.createElement("div");
1448             this._Lang.reserveNamespace("myfaces.config._autoeval");
1449             //null not swallowed
1450             myfaces.config._autoeval = false;
1451 
1452             var markup = "<script type='text/javascript'> myfaces.config._autoeval = true; </script>";
1453             //now we rely on the same replacement mechanisms as outerhtml because
1454             //some browsers have different behavior of embedded scripts in the contextualfragment
1455             //or innerhtml case (opera for instance), this way we make sure the
1456             //eval detection is covered correctly
1457             this.setAttribute(evalDiv, "style", "display:none");
1458 
1459             //it is less critical in some browsers (old ie versions)
1460             //to append as first element than as last
1461             //it should not make any difference layoutwise since we are on display none anyway.
1462             this.insertFirst(evalDiv);
1463 
1464             //we remap it into a real boolean value
1465             if (window.Range
1466                     && typeof Range.prototype.createContextualFragment == 'function') {
1467                 this._outerHTMLCompliant(evalDiv, markup);
1468             } else {
1469                 this._outerHTMLNonCompliant(evalDiv, markup);
1470             }
1471 
1472         }
1473 
1474         return  !myfaces.config._autoeval;
1475         /* var d = _this.browser;
1476 
1477 
1478          return (_this.exists(d, "isIE") &&
1479          ( d.isIE > 5.5)) ||
1480          //firefox at version 4 beginning has dropped
1481          //auto eval to be compliant with the rest
1482          (_this.exists(d, "isFF") &&
1483          (d.isFF > 3.9)) ||
1484          (_this.exists(d, "isKhtml") &&
1485          (d.isKhtml > 0)) ||
1486          (_this.exists(d, "isWebKit") &&
1487          (d.isWebKit > 0)) ||
1488          (_this.exists(d, "isSafari") &&
1489          (d.isSafari > 0));
1490          */
1491         //another way to determine this without direct user agent parsing probably could
1492         //be to add an embedded script tag programmatically and check for the script variable
1493         //set by the script if existing, the add went through an eval if not then we
1494         //have to deal with it ourselves, this might be dangerous in case of the ie however
1495         //so in case of ie we have to parse for all other browsers we can make a dynamic
1496         //check if the browser does auto eval
1497 
1498     }
1499     ,
1500 
1501     isMultipartCandidate: function(executes) {
1502         if (this._Lang.isString(executes)) {
1503             executes = this._Lang.strToArray(executes, /\s+/);
1504         }
1505 
1506         for (var exec in executes) {
1507             var element = this.byId(executes[exec]);
1508             var inputs = this.findByTagName(element, "input", true);
1509             for (var key in inputs) {
1510                 if (this.getAttribute(inputs[key], "type") == "file") return true;
1511             }
1512         }
1513         return false;
1514     }
1515     ,
1516 
1517 
1518     insertFirst: function(newNode) {
1519         var body = document.body;
1520         if (body.childNodes.length > 0) {
1521             body.insertBefore(newNode, body.firstChild);
1522         } else {
1523             body.appendChild(newNode);
1524         }
1525     }
1526     ,
1527 
1528     byId: function(id) {
1529         return this._Lang.byId(id);
1530     }
1531     ,
1532 
1533     getDummyPlaceHolder: function(markup) {
1534         var created = false;
1535         if (!this._dummyPlaceHolder) {
1536             this._dummyPlaceHolder = document.createElement("div");
1537             created = true;
1538         }
1539 
1540         //ieMobile in its 6.1-- incarnation cannot handle innerHTML detached objects so we have
1541         //to attach the dummy placeholder, we try to avoid it for
1542         //better browsers so that we do not have unecessary dom operations
1543         if (this._RT.browser.isIEMobile && created) {
1544             this.insertFirst(this._dummyPlaceHolder);
1545 
1546             this.setAttribute(this._dummyPlaceHolder, "style", "display: none");
1547 
1548         }
1549 
1550         return this._dummyPlaceHolder;
1551     },
1552 
1553     /**
1554      * fetches the window id for the current request
1555      * note, this is a preparation method for jsf 2.2
1556      */
1557     getWindowId: function() {
1558         var href = window.location.href;
1559         var windowId = "windowId";
1560         var regex = new RegExp("[\\?&]" + windowId + "=([^&#\\;]*)");
1561         var results = regex.exec(href);
1562         return (results != null) ? results[1] : null;
1563     }
1564 
1565 });
1566 
1567 
1568