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                     if(item.getAttribute("nonce")) {
 67                         newSS.setAttribute("nonce", item.getAttribute("nonce"));
 68                     }
 69 
 70                     document.getElementsByTagName("head")[0].appendChild(newSS);
 71                     //ie merrily again goes its own way
 72                     if (window.attachEvent && !_RT.isOpera && UDEF != typeof newSS.styleSheet && UDEF != newSS.styleSheet.cssText) newSS.styleSheet.cssText = style;
 73                     else newSS.appendChild(document.createTextNode(style));
 74                 },
 75 
 76                 execCss = function(item) {
 77                     var equalsIgnoreCase = _Lang.equalsIgnoreCase;
 78                     var tagName = item.tagName;
 79                     if (tagName && equalsIgnoreCase(tagName, "link") && equalsIgnoreCase(item.getAttribute("type"), "text/css")) {
 80                         applyStyle(item, "@import url('" + item.getAttribute("href") + "');");
 81                     } else if (tagName && equalsIgnoreCase(tagName, "style") && equalsIgnoreCase(item.getAttribute("type"), "text/css")) {
 82                         var innerText = [];
 83                         //compliant browsers know child nodes
 84                         var childNodes = item.childNodes;
 85                         if (childNodes) {
 86                             var len = childNodes.length;
 87                             for (var cnt = 0; cnt < len; cnt++) {
 88                                 innerText.push(childNodes[cnt].innerHTML || childNodes[cnt].data);
 89                             }
 90                             //non compliant ones innerHTML
 91                         } else if (item.innerHTML) {
 92                             innerText.push(item.innerHTML);
 93                         }
 94 
 95                         applyStyle(item, innerText.join(""));
 96                     }
 97                 };
 98 
 99         try {
100             var scriptElements = this.findByTagNames(item, {"link":1,"style":1}, true);
101             if (scriptElements == null) return;
102             for (var cnt = 0; cnt < scriptElements.length; cnt++) {
103                 execCss(scriptElements[cnt]);
104             }
105 
106         } finally {
107             //the usual ie6 fix code
108             //the IE6 garbage collector is broken
109             //nulling closures helps somewhat to reduce
110             //mem leaks, which are impossible to avoid
111             //at this browser
112             execCss = null;
113             applyStyle = null;
114         }
115     },
116 
117 
118     /**
119      * Run through the given Html item and execute the inline scripts
120      * (IE doesn't do this by itself)
121      * @param {Node} item
122      */
123     runScripts: function(item, xmlData) {
124         var _Lang = this._Lang,
125             _RT = this._RT,
126             finalScripts = [],
127             execScrpt = function(item) {
128                 var tagName = item.tagName;
129                 var type = item.type || "";
130                 //script type javascript has to be handled by eval, other types
131                 //must be handled by the browser
132                 if (tagName && _Lang.equalsIgnoreCase(tagName, "script") &&
133                         (type === "" ||
134                         _Lang.equalsIgnoreCase(type,"text/javascript") ||
135                         _Lang.equalsIgnoreCase(type,"javascript") ||
136                         _Lang.equalsIgnoreCase(type,"text/ecmascript") ||
137                         _Lang.equalsIgnoreCase(type,"ecmascript"))) {
138 
139                     var nonce = item.getAttribute("nonce") || null;
140 
141                     var src = item.getAttribute('src');
142                     if ('undefined' != typeof src
143                             && null != src
144                             && src.length > 0
145                             ) {
146                         //we have to move this into an inner if because chrome otherwise chokes
147                         //due to changing the and order instead of relying on left to right
148                         //if jsf.js is already registered we do not replace it anymore
149                         if ((src.indexOf("ln=scripts") == -1 && src.indexOf("ln=javax.faces") == -1) || (src.indexOf("/jsf.js") == -1
150                                 && src.indexOf("/jsf-uncompressed.js") == -1)) {
151 
152                             if (finalScripts.length) {
153                                 //script source means we have to eval the existing
154                                 //scripts before running the include
155                                 for(var cnt = 0; cnt < finalScripts.length; cnt++) {
156                                     _RT.globalEval(finalScripts[cnt].text, finalScripts[cnt]["cspMeta"] || null);
157                                 }
158 
159                                 finalScripts = [];
160                             }
161                             _RT.loadScriptEval(src, item.getAttribute('type'), false, "UTF-8", false, nonce ? {nonce: nonce} : null );
162                         }
163 
164                     } else {
165                         // embedded script auto eval
166                         var test = (!xmlData) ? item.text : _Lang.serializeChilds(item);
167                         var go = true;
168                         while (go) {
169                             go = false;
170                             if (test.substring(0, 1) == " ") {
171                                 test = test.substring(1);
172                                 go = true;
173                             }
174                             if (test.substring(0, 4) == "<!--") {
175                                 test = test.substring(4);
176                                 go = true;
177                             }
178                             if (test.substring(0, 11) == "//<![CDATA[") {
179                                 test = test.substring(11);
180                                 go = true;
181                             }
182                         }
183                         // we have to run the script under a global context
184                         //we store the script for less calls to eval
185                         finalScripts.push(nonce ? {
186                             cspMeta: {nonce: nonce},
187                             text: test
188                         }: {
189                             text: test
190                         });
191 
192                     }
193                 }
194             };
195         try {
196             var scriptElements = this.findByTagName(item, "script", true);
197             if (scriptElements == null) return;
198             for (var cnt = 0; cnt < scriptElements.length; cnt++) {
199                 execScrpt(scriptElements[cnt]);
200             }
201             if (finalScripts.length) {
202                 for(var cnt = 0; cnt < finalScripts.length; cnt++) {
203                     _RT.globalEval(finalScripts[cnt].text, finalScripts[cnt]["cspMeta"] || null);
204                 }
205             }
206         } catch (e) {
207             //we are now in accordance with the rest of the system of showing errors only in development mode
208             //the default error output is alert we always can override it with
209             //window.myfaces = window.myfaces || {};
210             //myfaces.config =  myfaces.config || {};
211             //myfaces.config.defaultErrorOutput = console.error;
212             if(jsf.getProjectStage() === "Development") {
213                 var defaultErrorOutput = myfaces._impl.core._Runtime.getGlobalConfig("defaultErrorOutput", alert);
214                 defaultErrorOutput("Error in evaluated javascript:"+ (e.message || e.description || e));
215             }
216         } finally {
217             //the usual ie6 fix code
218             //the IE6 garbage collector is broken
219             //nulling closures helps somewhat to reduce
220             //mem leaks, which are impossible to avoid
221             //at this browser
222             execScrpt = null;
223         }
224     },
225 
226 
227     /**
228      * determines to fetch a node
229      * from its id or name, the name case
230      * only works if the element is unique in its name
231      * @param {String} elem
232      */
233     byIdOrName: function(elem) {
234         if (!elem) return null;
235         if (!this._Lang.isString(elem)) return elem;
236 
237         var ret = this.byId(elem);
238         if (ret) return ret;
239         //we try the unique name fallback
240         var items = document.getElementsByName(elem);
241         return ((items.length == 1) ? items[0] : null);
242     },
243 
244     /**
245      * node id or name, determines the valid form identifier of a node
246      * depending on its uniqueness
247      *
248      * Usually the id is chosen for an elem, but if the id does not
249      * exist we try a name fallback. If the passed element has a unique
250      * name we can use that one as subsequent identifier.
251      *
252      *
253      * @param {String} elem
254      */
255     nodeIdOrName: function(elem) {
256         if (elem) {
257             //just to make sure that the pas
258 
259             elem = this.byId(elem);
260             if (!elem) return null;
261             //detached element handling, we also store the element name
262             //to get a fallback option in case the identifier is not determinable
263             // anymore, in case of a framework induced detachment the element.name should
264             // be shared if the identifier is not determinable anymore
265             //the downside of this method is the element name must be unique
266             //which in case of jsf it is
267             var elementId = elem.id || elem.name;
268             if ((elem.id == null || elem.id == '') && elem.name) {
269                 elementId = elem.name;
270 
271                 //last check for uniqueness
272                 if (document.getElementsByName(elementId).length > 1) {
273                     //no unique element name so we need to perform
274                     //a return null to let the caller deal with this issue
275                     return null;
276                 }
277             }
278             return elementId;
279         }
280         return null;
281     },
282 
283     deleteItems: function(items) {
284         if (! items || ! items.length) return;
285         for (var cnt = 0; cnt < items.length; cnt++) {
286             this.deleteItem(items[cnt]);
287         }
288     },
289 
290     /**
291      * Simple delete on an existing item
292      */
293     deleteItem: function(itemIdToReplace) {
294         var item = this.byId(itemIdToReplace);
295         if (!item) {
296             throw this._Lang.makeException(new Error(),null, null, this._nameSpace, "deleteItem",  "_Dom.deleteItem  Unknown Html-Component-ID: " + itemIdToReplace);
297         }
298 
299         this._removeNode(item, false);
300     },
301 
302     /**
303      * creates a node upon a given node name
304      * @param nodeName {String} the node name to be created
305      * @param attrs {Array} a set of attributes to be set
306      */
307     createElement: function(nodeName, attrs) {
308         var ret = document.createElement(nodeName);
309         if (attrs) {
310             for (var key in attrs) {
311                 if(!attrs.hasOwnProperty(key)) continue;
312                 this.setAttribute(ret, key, attrs[key]);
313             }
314         }
315         return ret;
316     },
317 
318     /**
319      * Checks whether the browser is dom compliant.
320      * Dom compliant means that it performs the basic dom operations safely
321      * without leaking and also is able to perform a native setAttribute
322      * operation without freaking out
323      *
324      *
325      * Not dom compliant browsers are all microsoft browsers in quirks mode
326      * and ie6 and ie7 to some degree in standards mode
327      * and pretty much every browser who cannot create ranges
328      * (older mobile browsers etc...)
329      *
330      * We dont do a full browser detection here because it probably is safer
331      * to test for existing features to make an assumption about the
332      * browsers capabilities
333      */
334     isDomCompliant: function() {
335         return true;
336     },
337 
338     /**
339      * proper insert before which takes tables into consideration as well as
340      * browser deficiencies
341      * @param item the node to insert before
342      * @param markup the markup to be inserted
343      */
344     insertBefore: function(item, markup) {
345         this._assertStdParams(item, markup, "insertBefore");
346 
347         markup = this._Lang.trim(markup);
348         if (markup === "") return null;
349 
350         var evalNodes = this._buildEvalNodes(item, markup),
351                 currentRef = item,
352                 parentNode = item.parentNode,
353                 ret = [];
354         for (var cnt = evalNodes.length - 1; cnt >= 0; cnt--) {
355             currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef);
356             ret.push(currentRef);
357         }
358         ret = ret.reverse();
359         this._eval(ret);
360         return ret;
361     },
362 
363     /**
364      * proper insert before which takes tables into consideration as well as
365      * browser deficiencies
366      * @param item the node to insert before
367      * @param markup the markup to be inserted
368      */
369     insertAfter: function(item, markup) {
370         this._assertStdParams(item, markup, "insertAfter");
371         markup = this._Lang.trim(markup);
372         if (markup === "") return null;
373 
374         var evalNodes = this._buildEvalNodes(item, markup),
375                 currentRef = item,
376                 parentNode = item.parentNode,
377                 ret = [];
378 
379         for (var cnt = 0; cnt < evalNodes.length; cnt++) {
380             if (currentRef.nextSibling) {
381                 //Winmobile 6 has problems with this strategy, but it is not really fixable
382                 currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef.nextSibling);
383             } else {
384                 currentRef = parentNode.appendChild(evalNodes[cnt]);
385             }
386             ret.push(currentRef);
387         }
388         this._eval(ret);
389         return ret;
390     },
391 
392     propertyToAttribute: function(name) {
393         if (name === 'className') {
394             return 'class';
395         } else if (name === 'xmllang') {
396             return 'xml:lang';
397         } else {
398             return name.toLowerCase();
399         }
400     },
401 
402     isFunctionNative: function(func) {
403         return /^\s*function[^{]+{\s*\[native code\]\s*}\s*$/.test(String(func));
404     },
405 
406     detectAttributes: function(element) {
407         //test if 'hasAttribute' method is present and its native code is intact
408         //for example, Prototype can add its own implementation if missing
409         //JSF 2.4 we now can reduce the complexity here, one of the functions now
410         //is definitely implemented
411         if (element.hasAttribute && this.isFunctionNative(element.hasAttribute)) {
412             return function(name) {
413                 return element.hasAttribute(name);
414             }
415         } else {
416             return function (name) {
417                 return !!element.getAttribute(name);
418             }
419         }
420     },
421 
422     /**
423      * copy all attributes from one element to another - except id
424      * @param target element to copy attributes to
425      * @param source element to copy attributes from
426      * @ignore
427      */
428     cloneAttributes: function(target, source) {
429 
430         // enumerate core element attributes - without 'dir' as special case
431         var coreElementProperties = ['className', 'title', 'lang', 'xmllang'];
432         // enumerate additional input element attributes
433         var inputElementProperties = [
434             'name', 'value', 'size', 'maxLength', 'src', 'alt', 'useMap', 'tabIndex', 'accessKey', 'accept', 'type'
435         ];
436         // enumerate additional boolean input attributes
437         var inputElementBooleanProperties = [
438             'checked', 'disabled', 'readOnly'
439         ];
440 
441         // Enumerate all the names of the event listeners
442         var listenerNames =
443             [ 'onclick', 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout',
444                 'onmouseover', 'onmouseup', 'onkeydown', 'onkeypress', 'onkeyup',
445                 'onhelp', 'onblur', 'onfocus', 'onchange', 'onload', 'onunload', 'onabort',
446                 'onreset', 'onselect', 'onsubmit'
447             ];
448 
449         var sourceAttributeDetector = this.detectAttributes(source);
450         var targetAttributeDetector = this.detectAttributes(target);
451 
452         var isInputElement = target.nodeName.toLowerCase() === 'input';
453         var propertyNames = isInputElement ? coreElementProperties.concat(inputElementProperties) : coreElementProperties;
454         var isXML = !source.ownerDocument.contentType || source.ownerDocument.contentType == 'text/xml';
455         for (var iIndex = 0, iLength = propertyNames.length; iIndex < iLength; iIndex++) {
456             var propertyName = propertyNames[iIndex];
457             var attributeName = this.propertyToAttribute(propertyName);
458             if (sourceAttributeDetector(attributeName)) {
459 
460                 //With IE 7 (quirks or standard mode) and IE 8/9 (quirks mode only),
461                 //you cannot get the attribute using 'class'. You must use 'className'
462                 //which is the same value you use to get the indexed property. The only
463                 //reliable way to detect this (without trying to evaluate the browser
464                 //mode and version) is to compare the two return values using 'className'
465                 //to see if they exactly the same.  If they are, then use the property
466                 //name when using getAttribute.
467                 if( attributeName == 'class'){
468                     if( this._RT.browser.isIE && (source.getAttribute(propertyName) === source[propertyName]) ){
469                         attributeName = propertyName;
470                     }
471                 }
472 
473                 var newValue = isXML ? source.getAttribute(attributeName) : source[propertyName];
474                 var oldValue = target[propertyName];
475                 if (oldValue != newValue) {
476                     target[propertyName] = newValue;
477                 }
478             } else {
479                 target.removeAttribute(attributeName);
480                 if (attributeName == "value") {
481                     target[propertyName] = '';
482                 }
483             }
484         }
485 
486         var booleanPropertyNames = isInputElement ? inputElementBooleanProperties : [];
487         for (var jIndex = 0, jLength = booleanPropertyNames.length; jIndex < jLength; jIndex++) {
488             var booleanPropertyName = booleanPropertyNames[jIndex];
489             var newBooleanValue = source[booleanPropertyName];
490             var oldBooleanValue = target[booleanPropertyName];
491             if (oldBooleanValue != newBooleanValue) {
492                 target[booleanPropertyName] = newBooleanValue;
493             }
494         }
495 
496         //'style' attribute special case
497         if (sourceAttributeDetector('style')) {
498             var newStyle;
499             var oldStyle;
500             if (this._RT.browser.isIE) {
501                 newStyle = source.style.cssText;
502                 oldStyle = target.style.cssText;
503                 if (newStyle != oldStyle) {
504                     target.style.cssText = newStyle;
505                 }
506             } else {
507                 newStyle = source.getAttribute('style');
508                 oldStyle = target.getAttribute('style');
509                 if (newStyle != oldStyle) {
510                     target.setAttribute('style', newStyle);
511                 }
512             }
513         } else if (targetAttributeDetector('style')){
514             target.removeAttribute('style');
515         }
516 
517         // Special case for 'dir' attribute
518         if (!this._RT.browser.isIE && source.dir != target.dir) {
519             if (sourceAttributeDetector('dir')) {
520                 target.dir = source.dir;
521             } else if (targetAttributeDetector('dir')) {
522                 target.dir = '';
523             }
524         }
525 
526         for (var lIndex = 0, lLength = listenerNames.length; lIndex < lLength; lIndex++) {
527             var name = listenerNames[lIndex];
528             target[name] = source[name] ? source[name] : null;
529             if (source[name]) {
530                 source[name] = null;
531             }
532         }
533 
534         //clone HTML5 data-* attributes
535         try{
536             var targetDataset = target.dataset;
537             var sourceDataset = source.dataset;
538             if (targetDataset || sourceDataset) {
539                 //cleanup the dataset
540                 for (var tp in targetDataset) {
541                     delete targetDataset[tp];
542                 }
543                 //copy dataset's properties
544                 for (var sp in sourceDataset) {
545                     targetDataset[sp] = sourceDataset[sp];
546                 }
547             }
548         } catch (ex) {
549             //most probably dataset properties are not supported
550         }
551     },
552     //from
553     // http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
554     getCaretPosition:function (ctrl) {
555         var caretPos = 0;
556 
557         try {
558 
559             // other browsers make it simpler by simply having a selection start element
560             if (ctrl.selectionStart || ctrl.selectionStart == '0')
561                 caretPos = ctrl.selectionStart;
562             // ie 5 quirks mode as second option because
563             // this option is flakey in conjunction with text areas
564             // TODO move this into the quirks class
565             else if (document.selection) {
566                 ctrl.focus();
567                 var selection = document.selection.createRange();
568                 //the selection now is start zero
569                 selection.moveStart('character', -ctrl.value.length);
570                 //the caretposition is the selection start
571                 caretPos = selection.text.length;
572             }
573         } catch (e) {
574             //now this is ugly, but not supported input types throw errors for selectionStart
575             //this way we are future proof by having not to define every selection enabled
576             //input in an if (which will be a lot in the near future with html5)
577         }
578         return caretPos;
579     },
580 
581     setCaretPosition:function (ctrl, pos) {
582 
583         if (ctrl.createTextRange) {
584             var range = ctrl.createTextRange();
585             range.collapse(true);
586             range.moveEnd('character', pos);
587             range.moveStart('character', pos);
588             range.select();
589         }
590         //IE quirks mode again, TODO move this into the quirks class
591         else if (ctrl.setSelectionRange) {
592             ctrl.focus();
593             //the selection range is our caret position
594             ctrl.setSelectionRange(pos, pos);
595         }
596     },
597 
598     /**
599      * outerHTML replacement which works cross browserlike
600      * but still is speed optimized
601      *
602      * @param item the item to be replaced
603      * @param markup the markup for the replacement
604      * @param preserveFocus, tries to preserve the focus within the outerhtml operation
605      * if set to true a focus preservation algorithm based on document.activeElement is
606      * used to preserve the focus at the exactly same location as it was
607      *
608      */
609     outerHTML : function(item, markup, preserveFocus) {
610         this._assertStdParams(item, markup, "outerHTML");
611         // we can work on a single element in a cross browser fashion
612         // regarding the focus thanks to the
613         // icefaces team for providing the code
614         if (item.nodeName.toLowerCase() === 'input') {
615             var replacingInput = this._buildEvalNodes(item, markup)[0];
616             this.cloneAttributes(item, replacingInput);
617             return item;
618         } else {
619             markup = this._Lang.trim(markup);
620             if (markup !== "") {
621                 var ret = null;
622 
623                 var focusElementId = null;
624                 var caretPosition = 0;
625                 if (preserveFocus && 'undefined' != typeof document.activeElement) {
626                     focusElementId = (document.activeElement) ? document.activeElement.id : null;
627                     caretPosition = this.getCaretPosition(document.activeElement);
628                 }
629                 // we try to determine the browsers compatibility
630                 // level to standards dom level 2 via various methods
631                 if (this.isDomCompliant()) {
632                     ret = this._outerHTMLCompliant(item, markup);
633                 } else {
634                     //call into abstract method
635                     ret = this._outerHTMLNonCompliant(item, markup);
636                 }
637                 if (focusElementId) {
638                     var newFocusElement = this.byId(focusElementId);
639                     if (newFocusElement && newFocusElement.nodeName.toLowerCase() === 'input') {
640                         //just in case the replacement element is not focusable anymore
641                         if ("undefined" != typeof newFocusElement.focus) {
642                             newFocusElement.focus();
643                         }
644                     }
645                     if (newFocusElement && caretPosition) {
646                         //zero caret position is set automatically on focus
647                         this.setCaretPosition(newFocusElement, caretPosition);
648                     }
649                 }
650 
651                 // and remove the old item
652                 //first we have to save the node newly insert for easier access in our eval part
653                 this._eval(ret);
654                 return ret;
655             }
656             // and remove the old item, in case of an empty newtag and do nothing else
657             this._removeNode(item, false);
658             return null;
659         }
660     },
661 
662     /**
663      * detaches a set of nodes from their parent elements
664      * in a browser independend manner
665      * @param {Object} items the items which need to be detached
666      * @return {Array} an array of nodes with the detached dom nodes
667      */
668     detach: function(items) {
669         var ret = [];
670         if ('undefined' != typeof items.nodeType) {
671             if (items.parentNode) {
672                 ret.push(items.parentNode.removeChild(items));
673             } else {
674                 ret.push(items);
675             }
676             return ret;
677         }
678         //all ies treat node lists not as arrays so we have to take
679         //an intermediate step
680         var nodeArr = this._Lang.objToArray(items);
681         for (var cnt = 0; cnt < nodeArr.length; cnt++) {
682             ret.push(nodeArr[cnt].parentNode.removeChild(nodeArr[cnt]));
683         }
684         return ret;
685     },
686 
687     _outerHTMLCompliant: function(item, markup) {
688         //table element replacements like thead, tbody etc... have to be treated differently
689         var evalNodes = this._buildEvalNodes(item, markup);
690 
691         if (evalNodes.length == 1) {
692             var ret = evalNodes[0];
693             item.parentNode.replaceChild(ret, item);
694             return ret;
695         } else {
696             return this.replaceElements(item, evalNodes);
697         }
698     },
699 
700     /**
701      * checks if the provided element is a subelement of a table element
702      * @param item
703      */
704     _isTableElement: function(item) {
705         return !!this.TABLE_ELEMS[(item.nodeName || item.tagName).toLowerCase()];
706     },
707 
708     /**
709      * non ie browsers do not have problems with embedded scripts or any other construct
710      * we simply can use an innerHTML in a placeholder
711      *
712      * @param markup the markup to be used
713      */
714     _buildNodesCompliant: function(markup) {
715         var dummyPlaceHolder = this.getDummyPlaceHolder(); //document.createElement("div");
716         dummyPlaceHolder.innerHTML = markup;
717         return this._Lang.objToArray(dummyPlaceHolder.childNodes);
718     },
719 
720 
721 
722 
723     /**
724      * builds up a correct dom subtree
725      * if the markup is part of table nodes
726      * The usecase for this is to allow subtable rendering
727      * like single rows thead or tbody
728      *
729      * @param item
730      * @param markup
731      */
732     _buildTableNodes: function(item, markup) {
733         var itemNodeName = (item.nodeName || item.tagName).toLowerCase();
734 
735         var tmpNodeName = itemNodeName;
736         var depth = 0;
737         while (tmpNodeName != "table") {
738             item = item.parentNode;
739             tmpNodeName = (item.nodeName || item.tagName).toLowerCase();
740             depth++;
741         }
742 
743         var dummyPlaceHolder = this.getDummyPlaceHolder();
744         if (itemNodeName == "td") {
745             dummyPlaceHolder.innerHTML = "<table><tbody><tr>" + markup + "</tr></tbody></table>";
746         } else {
747             dummyPlaceHolder.innerHTML = "<table>" + markup + "</table>";
748         }
749 
750         for (var cnt = 0; cnt < depth; cnt++) {
751             dummyPlaceHolder = dummyPlaceHolder.childNodes[0];
752         }
753 
754         return this.detach(dummyPlaceHolder.childNodes);
755     },
756 
757     _removeChildNodes: function(node /*, breakEventsOpen */) {
758         if (!node) return;
759         node.innerHTML = "";
760     },
761 
762 
763 
764     _removeNode: function(node /*, breakEventsOpen*/) {
765         if (!node) return;
766         var parentNode = node.parentNode;
767         if (parentNode) //if the node has a parent
768             parentNode.removeChild(node);
769     },
770 
771 
772     /**
773      * build up the nodes from html markup in a browser independend way
774      * so that it also works with table nodes
775      *
776      * @param item the parent item upon the nodes need to be processed upon after building
777      * @param markup the markup to be built up
778      */
779     _buildEvalNodes: function(item, markup) {
780         var evalNodes = null;
781         if (this._isTableElement(item)) {
782             evalNodes = this._buildTableNodes(item, markup);
783         } else {
784             var nonIEQuirks = (!this._RT.browser.isIE || this._RT.browser.isIE > 8);
785             //ie8 has a special problem it still has the swallow scripts and other
786             //elements bug, but it is mostly dom compliant so we have to give it a special
787             //treatment, IE9 finally fixes that issue finally after 10 years
788             evalNodes = (this.isDomCompliant() &&  nonIEQuirks) ?
789                     this._buildNodesCompliant(markup) :
790                     //ie8 or quirks mode browsers
791                     this._buildNodesNonCompliant(markup);
792         }
793         return evalNodes;
794     },
795 
796     /**
797      * we have lots of methods with just an item and a markup as params
798      * this method builds an assertion for those methods to reduce code
799      *
800      * @param item  the item to be tested
801      * @param markup the markup
802      * @param caller caller function
803      * @param {optional} params array of assertion param names
804      */
805     _assertStdParams: function(item, markup, caller, params) {
806         //internal error
807         if (!caller) {
808             throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "_assertStdParams",  "Caller must be set for assertion");
809         }
810         var _Lang = this._Lang,
811                 ERR_PROV = "ERR_MUST_BE_PROVIDED1",
812                 DOM = "myfaces._impl._util._Dom.",
813                 finalParams = params || ["item", "markup"];
814 
815         if (!item || !markup) {
816             _Lang.makeException(new Error(), null, null,DOM, ""+caller,  _Lang.getMessage(ERR_PROV, null, DOM +"."+ caller, (!item) ? finalParams[0] : finalParams[1]));
817             //throw Error(_Lang.getMessage(ERR_PROV, null, DOM + caller, (!item) ? params[0] : params[1]));
818         }
819     },
820 
821     /**
822      * internal eval handler used by various functions
823      * @param _nodeArr
824      */
825     _eval: function(_nodeArr) {
826         if (this.isManualScriptEval()) {
827             var isArr = _nodeArr instanceof Array;
828             if (isArr && _nodeArr.length) {
829                 for (var cnt = 0; cnt < _nodeArr.length; cnt++) {
830                     this.runScripts(_nodeArr[cnt]);
831                 }
832             } else if (!isArr) {
833                 this.runScripts(_nodeArr);
834             }
835         }
836     },
837 
838     /**
839      * for performance reasons we work with replaceElement and replaceElements here
840      * after measuring performance it has shown that passing down an array instead
841      * of a single node makes replaceElement twice as slow, however
842      * a single node case is the 95% case
843      *
844      * @param item
845      * @param evalNode
846      */
847     replaceElement: function(item, evalNode) {
848         //browsers with defect garbage collection
849         item.parentNode.insertBefore(evalNode, item);
850         this._removeNode(item, false);
851     },
852 
853 
854     /**
855      * replaces an element with another element or a set of elements
856      *
857      * @param item the item to be replaced
858      *
859      * @param evalNodes the elements
860      */
861     replaceElements: function (item, evalNodes) {
862         var evalNodesDefined = evalNodes && 'undefined' != typeof evalNodes.length;
863         if (!evalNodesDefined) {
864             throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "replaceElements",  this._Lang.getMessage("ERR_REPLACE_EL"));
865         }
866 
867         var parentNode = item.parentNode,
868 
869                 sibling = item.nextSibling,
870                 resultArr = this._Lang.objToArray(evalNodes);
871 
872         for (var cnt = 0; cnt < resultArr.length; cnt++) {
873             if (cnt == 0) {
874                 this.replaceElement(item, resultArr[cnt]);
875             } else {
876                 if (sibling) {
877                     parentNode.insertBefore(resultArr[cnt], sibling);
878                 } else {
879                     parentNode.appendChild(resultArr[cnt]);
880                 }
881             }
882         }
883         return resultArr;
884     },
885 
886     /**
887      * optimized search for an array of tag names
888      * deep scan will always be performed.
889      * @param fragment the fragment which should be searched for
890      * @param tagNames an map indx of tag names which have to be found
891      *
892      */
893     findByTagNames: function(fragment, tagNames) {
894         this._assertStdParams(fragment, tagNames, "findByTagNames", ["fragment", "tagNames"]);
895 
896         var nodeType = fragment.nodeType;
897         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
898 
899         //we can use the shortcut
900         if (fragment.querySelectorAll) {
901             var query = [];
902             for (var key in tagNames) {
903                 if(!tagNames.hasOwnProperty(key)) continue;
904                 query.push(key);
905             }
906             var res = [];
907             if (fragment.tagName && tagNames[fragment.tagName.toLowerCase()]) {
908                 res.push(fragment);
909             }
910             return res.concat(this._Lang.objToArray(fragment.querySelectorAll(query.join(", "))));
911         }
912 
913         //now the filter function checks case insensitively for the tag names needed
914         var filter = function(node) {
915             return node.tagName && tagNames[node.tagName.toLowerCase()];
916         };
917 
918         //now we run an optimized find all on it
919         try {
920             return this.findAll(fragment, filter, true);
921         } finally {
922             //the usual IE6 is broken, fix code
923             filter = null;
924         }
925     },
926 
927     /**
928      * determines the number of nodes according to their tagType
929      *
930      * @param {Node} fragment (Node or fragment) the fragment to be investigated
931      * @param {String} tagName the tag name (lowercase)
932      * (the normal usecase is false, which means if the element is found only its
933      * adjacent elements will be scanned, due to the recursive descension
934      * this should work out with elements with different nesting depths but not being
935      * parent and child to each other
936      *
937      * @return the child elements as array or null if nothing is found
938      *
939      */
940     findByTagName : function(fragment, tagName) {
941         this._assertStdParams(fragment, tagName, "findByTagName", ["fragment", "tagName"]);
942         var _Lang = this._Lang,
943                 nodeType = fragment.nodeType;
944         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
945 
946         //remapping to save a few bytes
947 
948         var ret = _Lang.objToArray(fragment.getElementsByTagName(tagName));
949         if (fragment.tagName && _Lang.equalsIgnoreCase(fragment.tagName, tagName)) ret.unshift(fragment);
950         return ret;
951     },
952 
953     findByName : function(fragment, name) {
954         this._assertStdParams(fragment, name, "findByName", ["fragment", "name"]);
955 
956         var nodeType = fragment.nodeType;
957         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
958 
959         var ret = this._Lang.objToArray(fragment.getElementsByName(name));
960         if (fragment.name == name) ret.unshift(fragment);
961         return ret;
962     },
963 
964     /**
965      * a filtered findAll for subdom treewalking
966      * (which uses browser optimizations wherever possible)
967      *
968      * @param {|Node|} rootNode the rootNode so start the scan
969      * @param filter filter closure with the syntax {boolean} filter({Node} node)
970      * @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)
971      */
972     findAll : function(rootNode, filter, deepScan) {
973         this._Lang.assertType(filter, "function");
974         deepScan = !!deepScan;
975 
976         if (document.createTreeWalker && NodeFilter) {
977             return this._iteratorSearchAll(rootNode, filter, deepScan);
978         } else {
979             //will not be called in dom level3 compliant browsers
980             return this._recursionSearchAll(rootNode, filter, deepScan);
981         }
982     },
983 
984     /**
985      * the faster dom iterator based search, works on all newer browsers
986      * except ie8 which already have implemented the dom iterator functions
987      * of html 5 (which is pretty all standard compliant browsers)
988      *
989      * The advantage of this method is a faster tree iteration compared
990      * to the normal recursive tree walking.
991      *
992      * @param rootNode the root node to be iterated over
993      * @param filter the iteration filter
994      * @param deepScan if set to true a deep scan is performed
995      */
996     _iteratorSearchAll: function(rootNode, filter, deepScan) {
997         var retVal = [];
998         //Works on firefox and webkit, opera and ie have to use the slower fallback mechanis
999         //we have a tree walker in place this allows for an optimized deep scan
1000         if (filter(rootNode)) {
1001 
1002             retVal.push(rootNode);
1003             if (!deepScan) {
1004                 return retVal;
1005             }
1006         }
1007         //we use the reject mechanism to prevent a deep scan reject means any
1008         //child elements will be omitted from the scan
1009         var FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT,
1010                 FILTER_SKIP = NodeFilter.FILTER_SKIP,
1011                 FILTER_REJECT = NodeFilter.FILTER_REJECT;
1012 
1013         var walkerFilter = function (node) {
1014             var retCode = (filter(node)) ? FILTER_ACCEPT : FILTER_SKIP;
1015             retCode = (!deepScan && retCode == FILTER_ACCEPT) ? FILTER_REJECT : retCode;
1016             if (retCode == FILTER_ACCEPT || retCode == FILTER_REJECT) {
1017                 retVal.push(node);
1018             }
1019             return retCode;
1020         };
1021 
1022         var treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, walkerFilter, false);
1023         //noinspection StatementWithEmptyBodyJS
1024         while (treeWalker.nextNode());
1025         return retVal;
1026     },
1027 
1028     /**
1029      * bugfixing for ie6 which does not cope properly with setAttribute
1030      */
1031     setAttribute : function(node, attr, val) {
1032         this._assertStdParams(node, attr, "setAttribute", ["fragment", "name"]);
1033         if (!node.setAttribute) {
1034             return;
1035         }
1036 
1037         if (attr === 'disabled') {
1038             node.disabled = val === 'disabled' || val === 'true';
1039         } else if (attr === 'checked') {
1040             node.checked = val === 'checked' || val === 'on' || val === 'true';
1041         } else if (attr == 'readonly') {
1042             node.readOnly = val === 'readonly' || val === 'true';
1043         } else {
1044             node.setAttribute(attr, val);
1045         }
1046     },
1047 
1048     /**
1049      * fuzzy form detection which tries to determine the form
1050      * an item has been detached.
1051      *
1052      * The problem is some Javascript libraries simply try to
1053      * detach controls by reusing the names
1054      * of the detached input controls. Most of the times,
1055      * the name is unique in a jsf scenario, due to the inherent form mapping.
1056      * One way or the other, we will try to fix that by
1057      * identifying the proper form over the name
1058      *
1059      * We do it in several ways, in case of no form null is returned
1060      * in case of multiple forms we check all elements with a given name (which we determine
1061      * out of a name or id of the detached element) and then iterate over them
1062      * to find whether they are in a form or not.
1063      *
1064      * If only one element within a form and a given identifier found then we can pull out
1065      * and move on
1066      *
1067      * We cannot do much further because in case of two identical named elements
1068      * all checks must fail and the first elements form is served.
1069      *
1070      * Note, this method is only triggered in case of the issuer or an ajax request
1071      * is a detached element, otherwise already existing code has served the correct form.
1072      *
1073      * This method was added because of
1074      * https://issues.apache.org/jira/browse/MYFACES-2599
1075      * to support the integration of existing ajax libraries which do heavy dom manipulation on the
1076      * controls side (Dojos Dijit library for instance).
1077      *
1078      * @param {Node} elem - element as source, can be detached, undefined or null
1079      *
1080      * @return either null or a form node if it could be determined
1081      *
1082      * TODO move this into extended and replace it with a simpler algorithm
1083      */
1084     fuzzyFormDetection : function(elem) {
1085         var forms = document.forms, _Lang = this._Lang;
1086 
1087         if (!forms || !forms.length) {
1088             return null;
1089         }
1090 
1091         // This will not work well on portlet case, because we cannot be sure
1092         // the returned form is right one.
1093         //we can cover that case by simply adding one of our config params
1094         //the default is the weaker, but more correct portlet code
1095         //you can override it with myfaces_config.no_portlet_env = true globally
1096         else if (1 == forms.length && this._RT.getGlobalConfig("no_portlet_env", false)) {
1097             return forms[0];
1098         }
1099 
1100         //before going into the more complicated stuff we try the simple approach
1101         var finalElem = this.byId(elem);
1102         var fetchForm = _Lang.hitch(this, function(elem) {
1103             //element of type form then we are already
1104             //at form level for the issuing element
1105             //https://issues.apache.org/jira/browse/MYFACES-2793
1106 
1107             return (_Lang.equalsIgnoreCase(elem.tagName, "form")) ? elem :
1108                     ( this.html5FormDetection(elem) || this.getParent(elem, "form"));
1109         });
1110 
1111         if (finalElem) {
1112             var elemForm = fetchForm(finalElem);
1113             if (elemForm) return elemForm;
1114         }
1115 
1116         /**
1117          * name check
1118          */
1119         var foundElements = [];
1120         var name = (_Lang.isString(elem)) ? elem : elem.name;
1121         //id detection did not work
1122         if (!name) return null;
1123         /**
1124          * the lesser chance is the elements which have the same name
1125          * (which is the more likely case in case of a brute dom replacement)
1126          */
1127         var nameElems = document.getElementsByName(name);
1128         if (nameElems) {
1129             for (var cnt = 0; cnt < nameElems.length && foundElements.length < 2; cnt++) {
1130                 // we already have covered the identifier case hence we only can deal with names,
1131                 var foundForm = fetchForm(nameElems[cnt]);
1132                 if (foundForm) {
1133                     foundElements.push(foundForm);
1134                 }
1135             }
1136         }
1137 
1138         return (1 == foundElements.length ) ? foundElements[0] : null;
1139     },
1140 
1141     html5FormDetection:function (item) {
1142         var elemForm = this.getAttribute(item, "form");
1143         return (elemForm) ? this.byId(elemForm) : null;
1144     },
1145 
1146 
1147     /**
1148      * gets a parent of an item with a given tagname
1149      * @param {Node} item - child element
1150      * @param {String} tagName - TagName of parent element
1151      */
1152     getParent : function(item, tagName) {
1153 
1154         if (!item) {
1155             throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "getParent",
1156                     this._Lang.getMessage("ERR_MUST_BE_PROVIDED1", null, "_Dom.getParent", "item {DomNode}"));
1157         }
1158 
1159         var _Lang = this._Lang;
1160         var searchClosure = function(parentItem) {
1161             return parentItem && parentItem.tagName
1162                     && _Lang.equalsIgnoreCase(parentItem.tagName, tagName);
1163         };
1164         try {
1165             return this.getFilteredParent(item, searchClosure);
1166         } finally {
1167             searchClosure = null;
1168             _Lang = null;
1169         }
1170     },
1171 
1172     /**
1173      * A parent walker which uses
1174      * a filter closure for filtering
1175      *
1176      * @param {Node} item the root item to ascend from
1177      * @param {function} filter the filter closure
1178      */
1179     getFilteredParent : function(item, filter) {
1180         this._assertStdParams(item, filter, "getFilteredParent", ["item", "filter"]);
1181 
1182         //search parent tag parentName
1183         var parentItem = (item.parentNode) ? item.parentNode : null;
1184 
1185         while (parentItem && !filter(parentItem)) {
1186             parentItem = parentItem.parentNode;
1187         }
1188         return (parentItem) ? parentItem : null;
1189     },
1190 
1191     /**
1192      * cross ported from dojo
1193      * fetches an attribute from a node
1194      *
1195      * @param {String} node the node
1196      * @param {String} attr the attribute
1197      * @return the attributes value or null
1198      */
1199     getAttribute : function(/* HTMLElement */node, /* string */attr) {
1200         return node.getAttribute(attr);
1201     },
1202 
1203     /**
1204      * checks whether the given node has an attribute attached
1205      *
1206      * @param {String|Object} node the node to search for
1207      * @param {String} attr the attribute to search for
1208      * @true if the attribute was found
1209      */
1210     hasAttribute : function(/* HTMLElement */node, /* string */attr) {
1211         //	summary
1212         //	Determines whether or not the specified node carries a value for the attribute in question.
1213         return this.getAttribute(node, attr) ? true : false;	//	boolean
1214     },
1215 
1216     /**
1217      * concatenation routine which concats all childnodes of a node which
1218      * contains a set of CDATA blocks to one big string
1219      * @param {Node} node the node to concat its blocks for
1220      */
1221     concatCDATABlocks : function(/*Node*/ node) {
1222         var cDataBlock = [];
1223         // response may contain several blocks
1224         for (var i = 0; i < node.childNodes.length; i++) {
1225             cDataBlock.push(node.childNodes[i].data);
1226         }
1227         return cDataBlock.join('');
1228     },
1229 
1230     //all modern browsers evaluate the scripts
1231     //manually this is a w3d recommendation
1232     isManualScriptEval: function() {
1233         return true;
1234     },
1235 
1236     /**
1237      * jsf2.2
1238      * checks if there is a fileupload element within
1239      * the executes list
1240      *
1241      * @param executes the executes list
1242      * @return {Boolean} true if there is a fileupload element
1243      */
1244     isMultipartCandidate:function (executes) {
1245         if (this._Lang.isString(executes)) {
1246             executes = this._Lang.strToArray(executes, /\s+/);
1247         }
1248 
1249         for (var cnt = 0, len = executes.length; cnt < len ; cnt ++) {
1250             var element = this.byId(executes[cnt]);
1251             var inputs = this.findByTagName(element, "input", true);
1252             for (var cnt2 = 0, len2 = inputs.length; cnt2 < len2 ; cnt2++) {
1253                 if (this.getAttribute(inputs[cnt2], "type") == "file") return true;
1254             }
1255         }
1256         return false;
1257     },
1258 
1259     insertFirst: function(newNode) {
1260         var body = document.body;
1261         if (body.childNodes.length > 0) {
1262             body.insertBefore(newNode, body.firstChild);
1263         } else {
1264             body.appendChild(newNode);
1265         }
1266     },
1267 
1268     byId: function(id) {
1269         return this._Lang.byId(id);
1270     },
1271 
1272     getDummyPlaceHolder: function() {
1273         this._dummyPlaceHolder = this._dummyPlaceHolder ||this.createElement("div");
1274         return this._dummyPlaceHolder;
1275     },
1276 
1277     getNamedElementFromForm: function(form, elementId) {
1278         return form[elementId];
1279     }
1280 });
1281 
1282 
1283