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