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 _AjaxResponse
 20  * @memberOf myfaces._impl.xhrCore
 21  * @extends myfaces._impl.core.Object
 22  * @description
 23  * This singleton is responsible for handling the standardized xml ajax response
 24  * Note: since the semantic processing can be handled about 90% in a functional
 25  * style we make this class stateless. Every state information is stored
 26  * temporarily in the context.
 27  *
 28  * The singleton approach also improves performance
 29  * due to less object gc compared to the old instance approach.
 30  *
 31  */
 32 _MF_SINGLTN(_PFX_XHR + "_AjaxResponse", _MF_OBJECT, /** @lends myfaces._impl.xhrCore._AjaxResponse.prototype */ {
 33 
 34     /*partial response types*/
 35     RESP_PARTIAL:"partial-response",
 36     RESP_TYPE_ERROR:"error",
 37     RESP_TYPE_REDIRECT:"redirect",
 38     RESP_TYPE_CHANGES:"changes",
 39 
 40     /*partial commands*/
 41     CMD_CHANGES:"changes",
 42     CMD_UPDATE:"update",
 43     CMD_DELETE:"delete",
 44     CMD_INSERT:"insert",
 45     CMD_EVAL:"eval",
 46     CMD_ERROR:"error",
 47     CMD_ATTRIBUTES:"attributes",
 48     CMD_EXTENSION:"extension",
 49     CMD_REDIRECT:"redirect",
 50 
 51     /*other constants*/
 52     P_VIEWSTATE:"javax.faces.ViewState",
 53     P_CLIENTWINDOW: "javax.faces.ClientWindow",
 54     P_VIEWROOT:"javax.faces.ViewRoot",
 55     P_VIEWHEAD:"javax.faces.ViewHead",
 56     P_VIEWBODY:"javax.faces.ViewBody",
 57     P_RESOURCE:"javax.faces.Resource",
 58 
 59     /**
 60      * uses response to start Html element replacement
 61      *
 62      * @param {Object} request (xhrRequest) - xhr request object
 63      * @param {Object} context (Map) - AJAX context
 64      *
 65      * A special handling has to be added to the update cycle
 66      * according to the JSDoc specs if the CDATA block contains html tags the outer rim must be stripped
 67      * if the CDATA block contains a head section the document head must be replaced
 68      * and if the CDATA block contains a body section the document body must be replaced!
 69      *
 70      */
 71     processResponse:function (request, context) {
 72         //mfinternal handling, note, the mfinternal is only optional
 73         //according to the spec
 74         context._mfInternal = context._mfInternal || {};
 75         var mfInternal = context._mfInternal;
 76 
 77         //the temporary data is hosted here
 78         mfInternal._updateElems = [];
 79         mfInternal._updateForms = [];
 80         mfInternal.appliedViewState = null;
 81         mfInternal.appliedClientWindow = null;
 82 
 83         try {
 84             var _Impl = this.attr("impl"), _Lang = this._Lang;
 85             // TODO:
 86             // Solution from
 87             // http://www.codingforums.com/archive/index.php/t-47018.html
 88             // to solve IE error 1072896658 when a Java server sends iso88591
 89             // istead of ISO-8859-1
 90 
 91             if (!request || !_Lang.exists(request, "responseXML")) {
 92                 throw this.makeException(new Error(), _Impl.EMPTY_RESPONSE, _Impl.EMPTY_RESPONSE, this._nameSpace, "processResponse", "");
 93             }
 94             //check for a parseError under certain browsers
 95 
 96             var xmlContent = request.responseXML;
 97             //ie6+ keeps the parsing response under xmlContent.parserError
 98             //while the rest of the world keeps it as element under the first node
 99             var xmlErr = _Lang.fetchXMLErrorMessage(request.responseText || request.response, xmlContent)
100             if (xmlErr) {
101                 throw this._raiseError(new Error(), xmlErr.errorMessage + "\n" + xmlErr.sourceText + "\n" + xmlErr.visualError + "\n", "processResponse");
102             }
103             var partials = xmlContent.childNodes[0];
104             if ('undefined' == typeof partials || partials == null) {
105                 throw this._raiseError(new Error(), "No child nodes for response", "processResponse");
106 
107             } else {
108                 if (partials.tagName != this.RESP_PARTIAL) {
109                     // IE 8 sees XML Header as first sibling ...
110                     partials = partials.nextSibling;
111                     if (!partials || partials.tagName != this.RESP_PARTIAL) {
112                         throw this._raiseError(new Error(), "Partial response not set", "processResponse");
113                     }
114                 }
115             }
116 
117             var childNodesLength = partials.childNodes.length;
118 
119             for (var loop = 0; loop < childNodesLength; loop++) {
120                 var childNode = partials.childNodes[loop];
121                 var tagName = childNode.tagName;
122                 /**
123                  * <eval>
124                  *      <![CDATA[javascript]]>
125                  * </eval>
126                  */
127 
128                 //this ought to be enough for eval
129                 //however the run scripts still makes sense
130                 //in the update and insert area for components
131                 //which do not use the response writer properly
132                 //we might add this one as custom option in update and
133                 //insert!
134                 if (tagName == this.CMD_ERROR) {
135                     this.processError(request, context, childNode);
136                 } else if (tagName == this.CMD_REDIRECT) {
137                     this.processRedirect(request, context, childNode);
138                 } else if (tagName == this.CMD_CHANGES) {
139                     this.processChanges(request, context, childNode);
140                 }
141             }
142 
143             //fixup missing viewStates due to spec deficiencies
144             if(mfInternal.appliedViewState) {
145                 this.fixViewStates(context);
146             }
147             if(mfInternal.appliedClientWindow) {
148                 this.fixClientWindows(context);
149             }
150 
151             //spec jsdoc, the success event must be sent from response
152             _Impl.sendEvent(request, context, _Impl["SUCCESS"]);
153 
154         } finally {
155             delete mfInternal._updateElems;
156             delete mfInternal._updateForms;
157             delete mfInternal.appliedViewState;
158             delete mfInternal.appliedClientWindow;
159         }
160     },
161 
162     /**
163      * fixes the viewstates in the current page
164      *
165      * @param context
166      */
167     fixViewStates:function (context) {
168         var _Lang = this._Lang;
169         var mfInternal = context._mfInternal;
170 
171         if (null == mfInternal.appliedViewState) {
172             return;
173         }
174 
175         //if we set our no portlet env we safely can update all forms with
176         //the new viewstate
177         if (this._RT.getLocalOrGlobalConfig(context, "no_portlet_env", false)) {
178             for (var cnt = document.forms.length - 1; cnt >= 0; cnt--) {
179                 this._setVSTCWForm(context, document.forms[cnt], mfInternal.appliedViewState, this.P_VIEWSTATE);
180             }
181             return;
182         }
183 
184         var updatedForm = this._getUpdatedForm(context, mfInternal._updateForms);
185         if (updatedForm != null) {
186             var baseViewStateField = this._Dom.getNamedElementFromForm(updatedForm, this.P_VIEWSTATE);
187             var viewStateId = baseViewStateField.id;
188             var viewStatePrefix = viewStateId.substring(0,
189                 viewStateId.indexOf(this.P_VIEWSTATE)+this.P_VIEWSTATE.length);
190             var viewStateFields = document.getElementsByName(this.P_VIEWSTATE);
191             for (var cnt = viewStateFields.length - 1; cnt >= 0; cnt--) {
192                 if (viewStateFields[cnt].id.startsWith(viewStatePrefix)) {
193                     this._setVSTCWForm(context, viewStateFields[cnt].form, mfInternal.appliedViewState, this.P_VIEWSTATE);
194                 }
195             }
196         }else{
197             // Now update the forms that were not replaced but forced to be updated, because contains child ajax tags
198             // we should only update forms with view state hidden field. If by some reason, the form was set to be
199             // updated but the form was replaced, it does not have hidden view state, so later in changeTrace processing the
200             // view state is updated.
201 
202             //set the viewstates of all outer forms parents of our updated elements
203             _Lang.arrForEach(mfInternal._updateForms, function (elem) {
204                 this._setVSTCWForm(context, elem, mfInternal.appliedViewState, this.P_VIEWSTATE);
205             }, 0, this);
206 
207             //set the viewstate of all forms within our updated elements
208             _Lang.arrForEach(mfInternal._updateElems, function (elem) {
209                 this._setVSTCWInnerForms(context, elem, mfInternal.appliedViewState, this.P_VIEWSTATE);
210             }, 0, this);
211         }
212     },
213 
214     fixClientWindows:function (context, theForm) {
215         var _Lang = this._Lang;
216         var mfInternal = context._mfInternal;
217 
218         if (null == mfInternal.appliedClientWindow) {
219             return;
220         }
221          //if we set our no portlet env we safely can update all forms with
222         //the new viewstate
223         if (this._RT.getLocalOrGlobalConfig(context, "no_portlet_env", false)) {
224             for (var cnt = document.forms.length - 1; cnt >= 0; cnt--) {
225                 this._setVSTCWForm(context, document.forms[cnt], mfInternal.appliedClientWindow, this.P_CLIENTWINDOW);
226             }
227             return;
228         }
229         
230         var updatedForm = this._getUpdatedForm(context, mfInternal._updateForms);
231         if (updatedForm != null) {
232             var baseCWField = this._Dom.getNamedElementFromForm(updatedForm, this.P_CLIENTWINDOW);
233             var cwId = baseCWField.id;
234             var cwPrefix = cwId.substring(0,
235                 cwId.indexOf(this.P_CLIENTWINDOW)+this.P_CLIENTWINDOW.length);
236             var cwFields = document.getElementsByName(this.P_CLIENTWINDOW);
237             for (var cnt = cwFields.length - 1; cnt >= 0; cnt--) {
238                 if (cwFields[cnt].id.startsWith(cwPrefix)) {
239                     this._setVSTCWForm(context, cwFields[cnt].form, mfInternal.appliedClientWindow, this.P_CLIENTWINDOW);
240                 }
241             }
242         } else{
243             //set the client window of all outer form of updated elements
244 
245             _Lang.arrForEach(mfInternal._updateForms, function (elem) {
246                 this._setVSTCWForm(context, elem, mfInternal.appliedClientWindow, this.P_CLIENTWINDOW);
247             }, 0, this);
248 
249             //set the client window of all forms within our updated elements
250             _Lang.arrForEach(mfInternal._updateElems, function (elem) {
251                 this._setVSTCWInnerForms(context, elem, mfInternal.appliedClientWindow, this.P_CLIENTWINDOW);
252             }, 0, this);
253         }
254     },
255 
256 
257     _getUpdatedForm:function(context, updateForms) {
258         if (updateForms != null) {
259             for (var i = 0; i < updateForms.length; i++) {
260                 var elem = this._Lang.byId(updateForms[i]);
261                 if (!elem){
262                     continue;
263                 }else{
264                     return elem;
265                 }
266             }
267         }
268     },
269     
270     /**
271      * sets the viewstate element in a given form
272      *
273      * @param theForm the form to which the element has to be set to
274      * @param context the current request context
275      */
276     _setVSTCWForm:function (context, theForm, value, identifier) {
277         if (typeof theForm === 'string' || theForm instanceof String) {
278             theForm = this._Lang.byId(theForm);
279         }
280         var mfInternal = context._mfInternal;
281 
282         if (!theForm) return;
283 
284         //in IE7 looking up form elements with complex names (such as 'javax.faces.ViewState') fails in certain cases
285         //iterate through the form elements to find the element, instead
286         var fieldToApply = this._Dom.getNamedElementFromForm(theForm, identifier);
287 
288         if (fieldToApply) {
289             this._Dom.setAttribute(fieldToApply, "value", value);
290         } else if (!fieldToApply) {
291             var element = this._Dom.getDummyPlaceHolder();
292             //spec error, two elements with the same id should not be there, TODO recheck the space if the name does not suffice alone
293             element.innerHTML = ["<input type='hidden'", "id='", identifier+jsf.separatorchar+Math.random() , "' name='", identifier , "' value='" , value , "' />"].join("");
294             //now we go to proper dom handling after having to deal with another ie screwup
295             try {
296                 theForm.appendChild(element.childNodes[0]);
297             } finally {
298                 element.innerHTML = "";
299             }
300         }
301     },
302 
303     _setVSTCWInnerForms:function (context, elem, value, identifier) {
304 
305         var _Lang = this._Lang, _Dom = this._Dom;
306         elem = _Dom.byIdOrName(elem);
307         //elem not found for whatever reason
308         //https://issues.apache.org/jira/browse/MYFACES-3544
309         if (!elem) return;
310 
311         var replacedForms = _Dom.findByTagName(elem, "form", false);
312 
313         var applyVST = _Lang.hitch(this, function (elem) {
314             this._setVSTCWForm(context, elem, value, identifier);
315         });
316 
317         try {
318             _Lang.arrForEach(replacedForms, applyVST, 0, this);
319         } finally {
320             applyVST = null;
321         }
322     },
323 
324     /**
325      * processes an incoming error from the response
326      * which is hosted under the <error> tag
327      * @param request the current request
328      * @param context the contect object
329      * @param node the node in the xml hosting the error message
330      */
331     processError:function (request, context, node) {
332         /**
333          * <error>
334          *      <error-name>String</error-name>
335          *      <error-message><![CDATA[message]]></error-message>
336          * <error>
337          */
338         var errorName = node.firstChild.textContent || node.firstChild.text || "",
339                 errorMessage = node.childNodes[1].firstChild.data || "";
340 
341         this.attr("impl").sendError(request, context, this.attr("impl").SERVER_ERROR, errorName, errorMessage, "myfaces._impl.xhrCore._AjaxResponse", "processError");
342     },
343 
344     /**
345      * processes an incoming xml redirect directive from the ajax response
346      * @param request the request object
347      * @param context the context
348      * @param node the node hosting the redirect data
349      */
350     processRedirect:function (request, context, node) {
351         /**
352          * <redirect url="url to redirect" />
353          */
354         var _Lang = this._Lang;
355         var redirectUrl = node.getAttribute("url");
356         if (!redirectUrl) {
357             throw this._raiseError(new Error(), _Lang.getMessage("ERR_RED_URL", null, "_AjaxResponse.processRedirect"), "processRedirect");
358         }
359         redirectUrl = _Lang.trim(redirectUrl);
360         if (redirectUrl == "") {
361             return false;
362         }
363         window.location = redirectUrl;
364         return true;
365     },
366 
367     /**
368      * main entry point for processing the changes
369      * it deals with the <changes> node of the
370      * response
371      *
372      * @param request the xhr request object
373      * @param context the context map
374      * @param node the changes node to be processed
375      */
376     processChanges:function (request, context, node) {
377         var changes = node.childNodes;
378         var _Lang = this._Lang;
379         //note we need to trace the changes which could affect our insert update or delete
380         //se that we can realign our ViewStates afterwards
381         //the realignment must happen post change processing
382 
383         for (var i = 0; i < changes.length; i++) {
384 
385             switch (changes[i].tagName) {
386 
387                 case this.CMD_UPDATE:
388                     this.processUpdate(request, context, changes[i]);
389                     break;
390                 case this.CMD_EVAL:
391                     _Lang.globalEval(changes[i].firstChild.data);
392                     break;
393                 case this.CMD_INSERT:
394                     this.processInsert(request, context, changes[i]);
395                     break;
396                 case this.CMD_DELETE:
397                     this.processDelete(request, context, changes[i]);
398                     break;
399                 case this.CMD_ATTRIBUTES:
400                     this.processAttributes(request, context, changes[i]);
401                     break;
402                 case this.CMD_EXTENSION:
403                     break;
404                 default:
405                     throw this._raiseError(new Error(), "_AjaxResponse.processChanges: Illegal Command Issued", "processChanges");
406             }
407         }
408 
409         return true;
410     },
411 
412     /**
413      * First sub-step process a pending update tag
414      *
415      * @param request the xhr request object
416      * @param context the context map
417      * @param node the changes node to be processed
418      */
419     processUpdate:function (request, context, node) {
420         if ( (node.getAttribute('id').indexOf(this.P_VIEWSTATE) != -1) || (node.getAttribute('id').indexOf(this.P_CLIENTWINDOW) != -1) ) {
421             //update the submitting forms viewstate to the new value
422             // The source form has to be pulled out of the CURRENT document first because the context object
423             // may refer to an invalid document if an update of the entire body has occurred before this point.
424             var mfInternal = context._mfInternal,
425                     fuzzyFormDetection = this._Lang.hitch(this._Dom, this._Dom.fuzzyFormDetection);
426             var elemId = (mfInternal._mfSourceControlId) ? mfInternal._mfSourceControlId :
427                     ((context.source) ? context.source.id : null);
428 
429             //theoretically a source of null can be given, then our form detection fails for
430             //the source element case and hence updateviewstate is skipped for the source
431             //form, but still render targets still can get the viewstate
432             var sourceForm = (mfInternal && mfInternal["_mfSourceFormId"] &&
433                     document.forms[mfInternal["_mfSourceFormId"]]) ?
434                     document.forms[mfInternal["_mfSourceFormId"]] : ((elemId) ? fuzzyFormDetection(elemId) : null);
435 
436             if(node.getAttribute('id').indexOf(this.P_VIEWSTATE) != -1) {
437                 mfInternal.appliedViewState = this._Dom.concatCDATABlocks(node);//node.firstChild.nodeValue;
438             } else if(node.getAttribute('id').indexOf(this.P_CLIENTWINDOW) != -1) {
439                 mfInternal.appliedClientWindow = node.firstChild.nodeValue;
440             }
441             //source form could not be determined either over the form identifer or the element
442             //we now skip this phase and just add everything we need for the fixup code
443 
444             if (!sourceForm) {
445                 //no source form found is not an error because
446                 //we might be able to recover one way or the other
447                 return true;
448             }
449 
450             mfInternal._updateForms.push(sourceForm.id);
451 
452         }
453         else {
454             // response may contain several blocks
455             var cDataBlock = this._Dom.concatCDATABlocks(node),
456                     resultNode = null,
457                     pushOpRes = this._Lang.hitch(this, this._pushOperationResult);
458 
459             switch (node.getAttribute('id')) {
460                 case this.P_VIEWROOT:
461 
462                     cDataBlock = cDataBlock.substring(cDataBlock.indexOf("<html"));
463 
464                     var parsedData = this._replaceHead(request, context, cDataBlock);
465 
466                     resultNode = ('undefined' != typeof parsedData && null != parsedData) ? this._replaceBody(request, context, cDataBlock, parsedData) : this._replaceBody(request, context, cDataBlock);
467                     if (resultNode) {
468                         pushOpRes(context, resultNode);
469                     }
470                     break;
471                 case this.P_VIEWHEAD:
472                     //we cannot replace the head, almost no browser allows this, some of them throw errors
473                     //others simply ignore it or replace it and destroy the dom that way!
474                     this._replaceHead(request, context, cDataBlock);
475 
476                     break;
477                 case this.P_VIEWBODY:
478                     //we assume the cdata block is our body including the tag
479                     resultNode = this._replaceBody(request, context, cDataBlock);
480                     if (resultNode) {
481                         pushOpRes(context, resultNode);
482                     }
483                     break;
484                 case this.P_RESOURCE:
485                     
486                     this._addResourceToHead(request,context,cDataBlock);
487                     break;
488                 default:
489                     resultNode = this.replaceHtmlItem(request, context, node.getAttribute('id'), cDataBlock);
490                     if (resultNode) {
491                         pushOpRes(context, resultNode);
492                     }
493                     break;
494             }
495         }
496 
497         return true;
498     },
499 
500     _pushOperationResult:function (context, resultNode) {
501         var mfInternal = context._mfInternal;
502         var pushSubnode = this._Lang.hitch(this, function (currNode) {
503             var parentForm = this._Dom.getParent(currNode, "form");
504             //if possible we work over the ids
505             //so that elements later replaced are referenced
506             //at the latest possibility
507             if (null != parentForm) {
508                 mfInternal._updateForms.push(parentForm.id || parentForm);
509             }
510             else {
511                 mfInternal._updateElems.push(currNode.id || currNode);
512             }
513         });
514         var isArr = 'undefined' != typeof resultNode.length && 'undefined' == typeof resultNode.nodeType;
515         if (isArr && resultNode.length) {
516             for (var cnt = 0; cnt < resultNode.length; cnt++) {
517                 pushSubnode(resultNode[cnt]);
518             }
519         } else if (!isArr) {
520             pushSubnode(resultNode);
521         }
522 
523     },
524 
525     /**
526      * replaces a current head theoretically,
527      * pratically only the scripts are evaled anew since nothing else
528      * can be changed.
529      *
530      * @param request the current request
531      * @param context the ajax context
532      * @param newData the data to be processed
533      *
534      * @return an xml representation of the page for further processing if possible
535      */
536     _replaceHead:function (request, context, newData) {
537 
538         var _Lang = this._Lang,
539                 _Dom = this._Dom,
540                 isWebkit = this._RT.browser.isWebKit,
541         //we have to work around an xml parsing bug in Webkit
542         //see https://issues.apache.org/jira/browse/MYFACES-3061
543                 doc = (!isWebkit) ? _Lang.parseXML(newData) : null,
544                 newHead = null;
545 
546         if (!isWebkit && _Lang.isXMLParseError(doc)) {
547             doc = _Lang.parseXML(newData.replace(/<!\-\-[\s\n]*<!\-\-/g, "<!--").replace(/\/\/-->[\s\n]*\/\/-->/g, "//-->"));
548         }
549 
550         if (isWebkit || _Lang.isXMLParseError(doc)) {
551             //the standard xml parser failed we retry with the stripper
552             var parser = new (this._RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))();
553             var headData = parser.parse(newData, "head");
554             //We cannot avoid it here, but we have reduced the parsing now down to the bare minimum
555             //for further processing
556             newHead = _Lang.parseXML("<head>" + headData + "</head>");
557             //last and slowest option create a new head element and let the browser
558             //do its slow job
559             if (_Lang.isXMLParseError(newHead)) {
560                 try {
561                     newHead = _Dom.createElement("head");
562                     newHead.innerHTML = headData;
563                 } catch (e) {
564                     //we give up no further fallbacks
565                     throw this._raiseError(new Error(), "Error head replacement failed reason:" + e.toString(), "_replaceHead");
566                 }
567             }
568         } else {
569             //parser worked we go on
570             newHead = doc.getElementsByTagName("head")[0];
571         }
572 
573         var oldTags = _Dom.findByTagNames(document.getElementsByTagName("head")[0], {"link":true, "style":true});
574         _Dom.runCss(newHead, true);
575         _Dom.deleteItems(oldTags);
576 
577         //var oldTags = _Dom.findByTagNames(document.getElementsByTagName("head")[0], {"script": true});
578         //_Dom.deleteScripts(oldTags);
579         _Dom.runScripts(newHead, true);
580 
581         return doc;
582     },
583     
584     _addResourceToHead:function (request, context, newData) {
585         var lastHeadChildTag = document.getElementsByTagName("head")[0].lastChild;
586 
587         var replacementFragment = this._Dom.insertAfter(lastHeadChildTag, newData);
588         if (replacementFragment) {
589             this._pushOperationResult(context, replacementFragment);
590         }
591     },
592 
593     /**
594      * special method to handle the body dom manipulation,
595      * replacing the entire body does not work fully by simply adding a second body
596      * and by creating a range instead we have to work around that by dom creating a second
597      * body and then filling it properly!
598      *
599      * @param {Object} request our request object
600      * @param {Object} context (Map) the response context
601      * @param {String} newData the markup which replaces the old dom node!
602      * @param {Node} parsedData (optional) preparsed XML representation data of the current document
603      */
604     _replaceBody:function (request, context, newData /*varargs*/) {
605         var _RT = this._RT,
606                 _Dom = this._Dom,
607                 _Lang = this._Lang,
608 
609                 oldBody = document.getElementsByTagName("body")[0],
610                 placeHolder = document.createElement("div"),
611                 isWebkit = _RT.browser.isWebKit;
612 
613         placeHolder.id = "myfaces_bodyplaceholder";
614 
615         _Dom._removeChildNodes(oldBody);
616         oldBody.innerHTML = "";
617         oldBody.appendChild(placeHolder);
618 
619         var bodyData, doc = null, parser;
620 
621         //we have to work around an xml parsing bug in Webkit
622         //see https://issues.apache.org/jira/browse/MYFACES-3061
623         if (!isWebkit) {
624             doc = (arguments.length > 3) ? arguments[3] : _Lang.parseXML(newData);
625         }
626 
627         if (!isWebkit && _Lang.isXMLParseError(doc)) {
628             doc = _Lang.parseXML(newData.replace(/<!\-\-[\s\n]*<!\-\-/g, "<!--").replace(/\/\/-->[\s\n]*\/\/-->/g, "//-->"));
629         }
630 
631         if (isWebkit || _Lang.isXMLParseError(doc)) {
632             //the standard xml parser failed we retry with the stripper
633 
634             parser = new (_RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))();
635 
636             bodyData = parser.parse(newData, "body");
637         } else {
638             //parser worked we go on
639             var newBodyData = doc.getElementsByTagName("body")[0];
640 
641             //speedwise we serialize back into the code
642             //for code reduction, speedwise we will take a small hit
643             //there which we will clean up in the future, but for now
644             //this is ok, I guess, since replace body only is a small subcase
645             //bodyData = _Lang.serializeChilds(newBodyData);
646             var browser = _RT.browser;
647             if (!browser.isIEMobile || browser.isIEMobile >= 7) {
648                 //TODO check what is failing there
649                 for (var cnt = 0; cnt < newBodyData.attributes.length; cnt++) {
650                     var value = newBodyData.attributes[cnt].value;
651                     if (value)
652                         _Dom.setAttribute(oldBody, newBodyData.attributes[cnt].name, value);
653                 }
654             }
655         }
656         //we cannot serialize here, due to escape problems
657         //we must parse, this is somewhat unsafe but should be safe enough
658         parser = new (_RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))();
659         bodyData = parser.parse(newData, "body");
660 
661         var returnedElement = this.replaceHtmlItem(request, context, placeHolder, bodyData);
662 
663         if (returnedElement) {
664             this._pushOperationResult(context, returnedElement);
665         }
666         return returnedElement;
667     },
668 
669     /**
670      * Replaces HTML elements through others and handle errors if the occur in the replacement part
671      *
672      * @param {Object} request (xhrRequest)
673      * @param {Object} context (Map)
674      * @param {Object} itemIdToReplace (String|Node) - ID of the element to replace
675      * @param {String} markup - the new tag
676      */
677     replaceHtmlItem:function (request, context, itemIdToReplace, markup) {
678         var _Lang = this._Lang, _Dom = this._Dom;
679 
680         var item = (!_Lang.isString(itemIdToReplace)) ? itemIdToReplace :
681                 _Dom.byIdOrName(itemIdToReplace);
682 
683         if (!item) {
684             throw this._raiseError(new Error(), _Lang.getMessage("ERR_ITEM_ID_NOTFOUND", null, "_AjaxResponse.replaceHtmlItem", (itemIdToReplace) ? itemIdToReplace.toString() : "undefined"), "replaceHtmlItem");
685         }
686         return _Dom.outerHTML(item, markup, this._RT.getLocalOrGlobalConfig(context, "preserveFocus", false));
687     },
688 
689     /**
690      * xml insert command handler
691      *
692      * @param request the ajax request element
693      * @param context the context element holding the data
694      * @param node the xml node holding the insert data
695      * @return true upon successful completion, false otherwise
696      *
697      **/
698     processInsert:function (request, context, node) {
699         /*remapping global namespaces for speed and readability reasons*/
700         var _Dom = this._Dom,
701                 _Lang = this._Lang,
702         //determine which path to go:
703                 insertData = this._parseInsertData(request, context, node);
704 
705         if (!insertData) return false;
706 
707         var opNode = _Dom.byIdOrName(insertData.opId);
708         if (!opNode) {
709             throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_INSERTBEFID_1", null, "_AjaxResponse.processInsert", insertData.opId), "processInsert");
710         }
711 
712         //call insertBefore or insertAfter in our dom routines
713         var replacementFragment = _Dom[insertData.insertType](opNode, insertData.cDataBlock);
714         if (replacementFragment) {
715             this._pushOperationResult(context, replacementFragment);
716         }
717         return true;
718     },
719 
720     /**
721      * determines the corner data from the insert tag parsing process
722      *
723      *
724      * @param request request
725      * @param context context
726      * @param node the current node pointing to the insert tag
727      * @return false if the parsing failed, otherwise a map with follwing attributes
728      * <ul>
729      *     <li>inserType - a ponter to a constant which maps the direct function name for the insert operation </li>
730      *     <li>opId - the before or after id </li>
731      *     <li>cDataBlock - the html cdata block which needs replacement </li>
732      * </ul>
733      *
734      * TODO we have to find a mechanism to replace the direct sendError calls with a javascript exception
735      * which we then can use for cleaner error code handling
736      */
737     _parseInsertData:function (request, context, node) {
738         var _Lang = this._Lang,
739                 _Dom = this._Dom,
740                 concatCDATA = _Dom.concatCDATABlocks,
741 
742                 INSERT_TYPE_BEFORE = "insertBefore",
743                 INSERT_TYPE_AFTER = "insertAfter",
744 
745                 id = node.getAttribute("id"),
746                 beforeId = node.getAttribute("before"),
747                 afterId = node.getAttribute("after"),
748                 ret = {};
749 
750         //now we have to make a distinction between two different parsing paths
751         //due to a spec malalignment
752         //a <insert id="... beforeId|AfterId ="...
753         //b <insert><before id="..., <insert> <after id="....
754         //see https://issues.apache.org/jira/browse/MYFACES-3318
755         //simple id, case1
756         if (id && beforeId && !afterId) {
757             ret.insertType = INSERT_TYPE_BEFORE;
758             ret.opId = beforeId;
759             ret.cDataBlock = concatCDATA(node);
760 
761             //<insert id=".. afterId="..
762         } else if (id && !beforeId && afterId) {
763             ret.insertType = INSERT_TYPE_AFTER;
764             ret.opId = afterId;
765             ret.cDataBlock = concatCDATA(node);
766 
767             //<insert><before id="... <insert><after id="...
768         } else if (!id) {
769             var opType = node.childNodes[0].tagName;
770 
771             if (opType != "before" && opType != "after") {
772                 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_INSERTBEFID"), "_parseInsertData");
773             }
774             opType = opType.toLowerCase();
775             var beforeAfterId = node.childNodes[0].getAttribute("id");
776             ret.insertType = (opType == "before") ? INSERT_TYPE_BEFORE : INSERT_TYPE_AFTER;
777             ret.opId = beforeAfterId;
778             ret.cDataBlock = concatCDATA(node.childNodes[0]);
779         } else {
780             throw this._raiseError(new Error(), [_Lang.getMessage("ERR_PPR_IDREQ"),
781                                                  "\n ",
782                                                  _Lang.getMessage("ERR_PPR_INSERTBEFID")].join(""), "_parseInsertData");
783         }
784         ret.opId = _Lang.trim(ret.opId);
785         return ret;
786     },
787 
788     processDelete:function (request, context, node) {
789 
790         var _Lang = this._Lang,
791                 _Dom = this._Dom,
792                 deleteId = node.getAttribute('id');
793 
794         if (!deleteId) {
795             throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_UNKNOWNCID", null, "_AjaxResponse.processDelete", ""), "processDelete");
796         }
797 
798         var item = _Dom.byIdOrName(deleteId);
799         if (!item) {
800             throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_UNKNOWNCID", null, "_AjaxResponse.processDelete", deleteId), "processDelete");
801         }
802 
803         var parentForm = this._Dom.getParent(item, "form");
804         if (null != parentForm) {
805             context._mfInternal._updateForms.push(parentForm);
806         }
807         _Dom.deleteItem(item);
808 
809         return true;
810     },
811 
812     processAttributes:function (request, context, node) {
813         //we now route into our attributes function to bypass
814         //IE quirks mode incompatibilities to the biggest possible extent
815         //most browsers just have to do a setAttributes but IE
816         //behaves as usual not like the official standard
817         //myfaces._impl._util.this._Dom.setAttribute(domNode, attribute, value;
818 
819         var _Lang = this._Lang,
820         //<attributes id="id of element"> <attribute name="attribute name" value="attribute value" />* </attributes>
821                 elemId = node.getAttribute('id');
822 
823         if (!elemId) {
824             throw this._raiseError(new Error(), "Error in attributes, id not in xml markup", "processAttributes");
825         }
826         var childNodes = node.childNodes;
827 
828         if (!childNodes) {
829             return false;
830         }
831         for (var loop2 = 0; loop2 < childNodes.length; loop2++) {
832             var attributesNode = childNodes[loop2],
833                     attrName = attributesNode.getAttribute("name"),
834                     attrValue = attributesNode.getAttribute("value");
835 
836             if (!attrName) {
837                 continue;
838             }
839 
840             attrName = _Lang.trim(attrName);
841             /*no value means reset*/
842             //value can be of boolean value hence full check
843             if ('undefined' == typeof attrValue || null == attrValue) {
844                 attrValue = "";
845             }
846 
847             switch (elemId) {
848                 case this.P_VIEWROOT:
849                     throw  this._raiseError(new Error(), _Lang.getMessage("ERR_NO_VIEWROOTATTR", null, "_AjaxResponse.processAttributes"), "processAttributes");
850 
851                 case this.P_VIEWHEAD:
852                     throw  this._raiseError(new Error(), _Lang.getMessage("ERR_NO_HEADATTR", null, "_AjaxResponse.processAttributes"), "processAttributes");
853 
854                 case this.P_VIEWBODY:
855                     var element = document.getElementsByTagName("body")[0];
856                     this._Dom.setAttribute(element, attrName, attrValue);
857                     break;
858 
859                 default:
860                     this._Dom.setAttribute(document.getElementById(elemId), attrName, attrValue);
861                     break;
862             }
863         }
864         return true;
865     },
866 
867     /**
868      * internal helper which raises an error in the
869      * format we need for further processing
870      *
871      * @param message the message
872      * @param title the title of the error (optional)
873      * @param name the name of the error (optional)
874      */
875     _raiseError:function (error, message, caller, title, name) {
876         var _Impl = this.attr("impl");
877         var finalTitle = title || _Impl.MALFORMEDXML;
878         var finalName = name || _Impl.MALFORMEDXML;
879         var finalMessage = message || "";
880 
881         return this._Lang.makeException(error, finalTitle, finalName, this._nameSpace, caller || ( (arguments.caller) ? arguments.caller.toString() : "_raiseError"), finalMessage);
882     }
883 });
884