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