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 Impl
 20  * @memberOf myfaces._impl.core
 21  * @description Implementation singleton which implements all interface method
 22  * defined by our jsf.js API
 23  * */
 24 _MF_SINGLTN(_PFX_CORE + "Impl", _MF_OBJECT, /**  @lends myfaces._impl.core.Impl.prototype */ {
 25 
 26     //third option myfaces._impl.xhrCoreAjax which will be the new core impl for now
 27     _transport:myfaces._impl.core._Runtime.getGlobalConfig("transport", myfaces._impl.xhrCore._Transports),
 28 
 29     /**
 30      * external event listener queue!
 31      */
 32     _evtListeners:new (myfaces._impl.core._Runtime.getGlobalConfig("eventListenerQueue", myfaces._impl._util._ListenerQueue))(),
 33 
 34     /**
 35      * external error listener queue!
 36      */
 37     _errListeners:new (myfaces._impl.core._Runtime.getGlobalConfig("errorListenerQueue", myfaces._impl._util._ListenerQueue))(),
 38 
 39     /*CONSTANTS*/
 40 
 41     /*internal identifiers for options*/
 42     IDENT_ALL:"@all",
 43     IDENT_NONE:"@none",
 44     IDENT_THIS:"@this",
 45     IDENT_FORM:"@form",
 46 
 47     /*
 48      * [STATIC] constants
 49      */
 50 
 51     P_PARTIAL_SOURCE:"javax.faces.source",
 52     P_VIEWSTATE:"javax.faces.ViewState",
 53     P_CLIENTWINDOW:"javax.faces.ClientWindow",
 54     P_AJAX:"javax.faces.partial.ajax",
 55     P_EXECUTE:"javax.faces.partial.execute",
 56     P_RENDER:"javax.faces.partial.render",
 57     P_EVT:"javax.faces.partial.event",
 58     P_BEHAVIOR_EVENT:"javax.faces.behavior.event",
 59     P_WINDOW_ID:"javax.faces.ClientWindow",
 60     P_RESET_VALUES:"javax.faces.partial.resetValues",
 61 
 62     //faces std values
 63     STD_VALUES: [this.P_PARTIAL_SOURCE, this.P_VIEWSTATE, this.P_CLIENTWINDOW, this.P_AJAX,
 64         this.P_EXECUTE, this.P_RENDER, this.P_EVT, this.P_BEHAVIOR_EVENT, this.P_WINDOW_ID, this.P_RESET_VALUES],
 65 
 66     /* message types */
 67     ERROR:"error",
 68     EVENT:"event",
 69 
 70     /* event emitting stages */
 71     BEGIN:"begin",
 72     COMPLETE:"complete",
 73     SUCCESS:"success",
 74 
 75     /*ajax errors spec 14.4.2*/
 76     HTTPERROR:"httpError",
 77     EMPTY_RESPONSE:"emptyResponse",
 78     MALFORMEDXML:"malformedXML",
 79     SERVER_ERROR:"serverError",
 80     CLIENT_ERROR:"clientError",
 81     TIMEOUT_EVENT:"timeout",
 82 
 83     /*error reporting threshold*/
 84     _threshold:"ERROR",
 85 
 86     /*blockfilter for the passthrough filtering, the attributes given here
 87      * will not be transmitted from the options into the passthrough*/
 88     _BLOCKFILTER:{onerror:1, onevent:1, render:1, execute:1, myfaces:1, delay:1, resetValues:1, params: 1},
 89 
 90     /**
 91      * collect and encode data for a given form element (must be of type form)
 92      * find the javax.faces.ViewState element and encode its value as well!
 93      * return a concatenated string of the encoded values!
 94      *
 95      * @throws Error in case of the given element not being of type form!
 96      * https://issues.apache.org/jira/browse/MYFACES-2110
 97      */
 98     getViewState:function (form) {
 99         /**
100          *  typecheck assert!, we opt for strong typing here
101          *  because it makes it easier to detect bugs
102          */
103         if (form) {
104             form = this._Lang.byId(form);
105         }
106 
107         if (!form
108                 || !form.nodeName
109                 || form.nodeName.toLowerCase() != "form") {
110             throw new Error(this._Lang.getMessage("ERR_VIEWSTATE"));
111         }
112 
113         var ajaxUtils = myfaces._impl.xhrCore._AjaxUtils;
114 
115         var ret = this._Lang.createFormDataDecorator([]);
116         ajaxUtils.encodeSubmittableFields(ret, form, null);
117 
118         return ret.makeFinal();
119     },
120 
121     /**
122      * this function has to send the ajax requests
123      *
124      * following request conditions must be met:
125      * <ul>
126      *  <li> the request must be sent asynchronously! </li>
127      *  <li> the request must be a POST!!! request </li>
128      *  <li> the request url must be the form action attribute </li>
129      *  <li> all requests must be queued with a client side request queue to ensure the request ordering!</li>
130      * </ul>
131      *
132      * @param {String|Node} elem any dom element no matter being it html or jsf, from which the event is emitted
133      * @param {|Event|} event any javascript event supported by that object
134      * @param {|Object|} options  map of options being pushed into the ajax cycle
135      *
136      *
137      * a) transformArguments out of the function
138      * b) passThrough handling with a map copy with a filter map block map
139      */
140     request:function (elem, event, options) {
141         if (this._delayTimeout) {
142             clearTimeout(this._delayTimeout);
143             delete this._delayTimeout;
144         }
145         /*namespace remap for our local function context we mix the entire function namespace into
146          *a local function variable so that we do not have to write the entire namespace
147          *all the time
148          **/
149         var _Lang = this._Lang,
150             _Dom = this._Dom,
151             _Utils = myfaces._impl.xhrCore._AjaxUtils;
152         /*assert if the onerror is set and once if it is set it must be of type function*/
153         _Lang.assertType(options.onerror, "function");
154         /*assert if the onevent is set and once if it is set it must be of type function*/
155         _Lang.assertType(options.onevent, "function");
156 
157         //options not set we define a default one with nothing
158         options = options || {};
159 
160         /**
161          * we cross - reference statically hence the mapping here
162          * the entire mapping between the functions is stateless
163          */
164         //null definitely means no event passed down so we skip the ie specific checks
165         if ('undefined' == typeof event) {
166             event = window.event || null;
167         }
168 
169         //improve the error messages if an empty elem is passed
170         if (!elem) {
171             throw _Lang.makeException(new Error(), "ArgNotSet", null, this._nameSpace, "request", _Lang.getMessage("ERR_MUST_BE_PROVIDED1", "{0}: source  must be provided", "jsf.ajax.request", "source element id"));
172         }
173         var oldElem = elem;
174         elem = _Dom.byIdOrName(elem);
175         if (!elem) {
176             throw _Lang.makeException(new Error(), "Notfound", null, this._nameSpace, "request", _Lang.getMessage("ERR_PPR_UNKNOWNCID", "{0}: Node with id {1} could not be found from source", this._nameSpace + ".request", oldElem));
177         }
178 
179         var elementId = _Dom.nodeIdOrName(elem);
180 
181         /*
182          * We make a copy of our options because
183          * we should not touch the incoming params!
184          * this copy is also the pass through parameters
185          * which are sent down our request
186          */
187         // this is legacy behavior which is faulty, will be removed if we decide to do it
188         // that way
189         // TODO not sure whether we add the naming container prefix to the user params
190         var passThrgh = _Lang.mixMaps({}, options, true, this._BLOCKFILTER);
191         // jsdoc spec everything under params must be passed through
192         if(options.params)  {
193             passThrgh = _Lang.mixMaps(passThrgh, options.params, true, {});
194         }
195 
196         if (event) {
197             passThrgh[this.P_EVT] = event.type;
198         }
199 
200 
201 
202         /**
203          * ajax pass through context with the source
204          * onevent and onerror
205          */
206         var context = {
207             source:elem,
208             onevent:options.onevent,
209             onerror:options.onerror,
210             viewId: "",
211             //TODO move the myfaces part into the _mfInternal part
212             myfaces:options.myfaces,
213             _mfInternal:{}
214         };
215         //additional meta information to speed things up, note internal non jsf
216         //pass through options are stored under _mfInternal in the context
217         var mfInternal = context._mfInternal;
218 
219         /**
220          * fetch the parent form
221          *
222          * note we also add an override possibility here
223          * so that people can use dummy forms and work
224          * with detached objects
225          */
226         var form = (options.myfaces && options.myfaces.form) ?
227                 _Lang.byId(options.myfaces.form) :
228                 this._getForm(elem, event);
229 
230         context.viewId = this.getViewId(form);
231 
232         /**
233          * we also now assign the container data to deal with it later
234          */
235         _Utils._assignNamingContainerData(mfInternal, form, jsf.separatorchar);
236 
237 
238         /**
239          * JSF2.2 client window must be part of the issuing form so it is encoded
240          * automatically in the request
241          */
242         //we set the client window before encoding by a call to jsf.getClientWindow
243         var clientWindow = jsf.getClientWindow(form);
244         //in case someone decorates the getClientWindow we reset the value from
245         //what we are getting
246         if ('undefined' != typeof clientWindow && null != clientWindow) {
247             var formElem = _Dom.getNamedElementFromForm(form, _Utils._$ncRemap(mfInternal,  this.P_CLIENTWINDOW));
248             if (formElem) {
249                 //we store the value for later processing during the ajax phase
250                 //job so that we do not get double values
251                 context._mfInternal._clientWindow = jsf.getClientWindow(form);
252             } else {
253                 passThrgh[this.P_CLIENTWINDOW] = jsf.getClientWindow(form);
254             }
255         } /*  spec proposal
256         else {
257             var formElem = _Dom.getNamedElementFromForm(form, this.P_CLIENTWINDOW);
258             if (formElem) {
259                 context._mfInternal._clientWindow = "undefined";
260             } else {
261                 passThrgh[this.P_CLIENTWINDOW] = "undefined";
262             }
263         }
264         */
265 
266         /**
267          * binding contract the javax.faces.source must be set
268          */
269         passThrgh[this.P_PARTIAL_SOURCE] = elementId;
270 
271         /**
272          * javax.faces.partial.ajax must be set to true
273          */
274         passThrgh[this.P_AJAX] = true;
275 
276         /**
277          * if resetValues is set to true
278          * then we have to set javax.faces.resetValues as well
279          * as pass through parameter
280          * the value has to be explicitly true, according to
281          * the specs jsdoc
282          */
283         if(options.resetValues === true) {
284             passThrgh[this.P_RESET_VALUES] = true;
285         }
286 
287         if (options.execute) {
288             /*the options must be a blank delimited list of strings*/
289             /*compliance with Mojarra which automatically adds @this to an execute
290              * the spec rev 2.0a however states, if none is issued nothing at all should be sent down
291              */
292             options.execute = (options.execute.indexOf("@this") == -1) ? options.execute : options.execute;
293 
294             this._transformList(passThrgh, this.P_EXECUTE, options.execute, form, elementId, context.viewId);
295         } else {
296             passThrgh[this.P_EXECUTE] = elementId;
297         }
298 
299         if (options.render) {
300             this._transformList(passThrgh, this.P_RENDER, options.render, form, elementId, context.viewId);
301         }
302 
303         /**
304          * multiple transports upcoming jsf 2.x feature currently allowed
305          * default (no value) xhrQueuedPost
306          *
307          * xhrQueuedPost
308          * xhrPost
309          * xhrGet
310          * xhrQueuedGet
311          * iframePost
312          * iframeQueuedPost
313          *
314          */
315         var transportType = this._getTransportType(context, passThrgh, form);
316 
317         mfInternal["_mfSourceFormId"] = form.id;
318         mfInternal["_mfSourceControlId"] = elementId;
319         mfInternal["_mfTransportType"] = transportType;
320 
321         //mojarra compatibility, mojarra is sending the form id as well
322         //this is not documented behavior but can be determined by running
323         //mojarra under blackbox conditions
324         //i assume it does the same as our formId_submit=1 so leaving it out
325         //won´t hurt but for the sake of compatibility we are going to add it
326         passThrgh[form.id] = form.id;
327 
328         /* jsf2.2 only: options.delay || */
329 
330         // TCK 790 we now have to remap all passthroughs in case of a naming container
331         // thing is the naming container is always prefixed on inputs, and our own
332         // passthroughs are not mapped for now (if we have to do that we we have to add a similar mapping code)
333         var passthroughKeys = Object.keys(passThrgh);
334         for(var key in passthroughKeys) {
335             if(!Object.hasOwnProperty(key) || this.STD_VALUES.indexOf(key) == -1) {
336                 continue;
337             }
338             passThrgh[_Utils._$ncRemap(mfInternal, key)] = passThrgh[key];
339             delete passThrgh[key];
340         }
341 
342         /* faces2.2 only: options.delay || */
343         var delayTimeout = options.delay || this._RT.getLocalOrGlobalConfig(context, "delay", false);
344 
345         if (!!delayTimeout) {
346             if(delayTimeout.toLowerCase &&  delayTimeout.toLowerCase() === "none"){
347                 delayTimeout = 0;
348             }
349             if(!(delayTimeout >= 0)) {
350                 // abbreviation which covers all cases of non positive values,
351                 // including NaN and non-numeric strings, no type equality is deliberate here,
352                 throw new Error("Invalid delay value: " + delayTimeout);
353             }
354             if (this._delayTimeout) {
355                 clearTimeout(this._delayTimeout);
356             }
357             this._delayTimeout = setTimeout(_Lang.hitch(this, function () {
358                 this._transport[transportType](elem, form, context, passThrgh);
359                 this._delayTimeout = null;
360             }), parseInt(delayTimeout));
361         } else {
362             this._transport[transportType](elem, form, context, passThrgh);
363         }
364     },
365 
366     /**
367      * fetches the form in an unprecise manner depending
368      * on an element or event target
369      *
370      * @param elem
371      * @param event
372      */
373     _getForm:function (elem, event) {
374         var _Dom = this._Dom;
375         var _Lang = this._Lang;
376         var form = _Dom.fuzzyFormDetection(elem);
377 
378         if (!form && event) {
379             //in case of no form is given we retry over the issuing event
380             form = _Dom.fuzzyFormDetection(_Lang.getEventTarget(event));
381             if (!form) {
382                 throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getForm", _Lang.getMessage("ERR_FORM"));
383             }
384         } else if (!form) {
385             throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getForm", _Lang.getMessage("ERR_FORM"));
386 
387         }
388         return form;
389     },
390 
391     /**
392      * determines the transport type to be called
393      * for the ajax call
394      *
395      * @param context the context
396      * @param passThrgh  pass through values
397      * @param form the form which issues the request
398      */
399     _getTransportType:function (context, passThrgh, form) {
400         /**
401          * if execute or render exist
402          * we have to pass them down as a blank delimited string representation
403          * of an array of ids!
404          */
405         //for now we turn off the transport auto selection, to enable 2.0 backwards compatibility
406         //on protocol level, the file upload only can be turned on if the auto selection is set to true
407         var getConfig = this._RT.getLocalOrGlobalConfig,
408             _Lang = this._Lang,
409             _Dom = this._Dom;
410 
411         var transportAutoSelection = getConfig(context, "transportAutoSelection", true);
412         /*var isMultipart = (transportAutoSelection && _Dom.getAttribute(form, "enctype") == "multipart/form-data") ?
413          _Dom.isMultipartCandidate((!getConfig(context, "pps",false))? form : passThrgh[this.P_EXECUTE]) :
414          false;
415          **/
416         if (!transportAutoSelection) {
417             return getConfig(context, "transportType", "xhrQueuedPost");
418         }
419         var multiPartCandidate = _Dom.isMultipartCandidate((!getConfig(context, "pps", false)) ?
420             form : passThrgh[this.P_EXECUTE]);
421         var multipartForm = (_Dom.getAttribute(form, "enctype") || "").toLowerCase() == "multipart/form-data";
422         //spec section jsdoc, if we have a multipart candidate in our execute (aka fileupload)
423         //and the form is not multipart then we have to raise an error
424         if (multiPartCandidate && !multipartForm) {
425             throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getTransportType", _Lang.getMessage("ERR_NO_MULTIPART_FORM", "No Multipart form", form.id));
426         }
427         var isMultipart = multiPartCandidate && multipartForm;
428         /**
429          * multiple transports upcoming jsf 2.2 feature currently allowed
430          * default (no value) xhrQueuedPost
431          *
432          * xhrQueuedPost
433          * xhrPost
434          * xhrGet
435          * xhrQueuedGet
436          * iframePost
437          * iframeQueuedPost
438          *
439          */
440         var transportType = (!isMultipart) ?
441             getConfig(context, "transportType", "xhrQueuedPost") :
442             getConfig(context, "transportType", "multipartQueuedPost");
443         if (!this._transport[transportType]) {
444             //throw new Error("Transport type " + transportType + " does not exist");
445             throw new Error(_Lang.getMessage("ERR_TRANSPORT", null, transportType));
446         }
447         return transportType;
448 
449     },
450 
451     /**
452      * transforms the list to the expected one
453      * with the proper none all form and this handling
454      * (note we also could use a simple string replace but then
455      * we would have had double entries under some circumstances)
456      *
457      * @param passThrgh
458      * @param target
459      * @param srcStr
460      * @param form
461      * @param elementId
462      * @param namingContainerId the naming container namingContainerId
463      */
464     _transformList:function (passThrgh, target, srcStr, form, elementId, namingContainerId) {
465         var _Lang = this._Lang;
466         //this is probably the fastest transformation method
467         //it uses an array and an index to position all elements correctly
468         //the offset variable is there to prevent 0 which results in a javascript
469         //false
470         srcStr = this._Lang.trim(srcStr);
471         var offset = 1,
472             vals = (srcStr) ? srcStr.split(/\s+/) : [],
473             idIdx = (vals.length) ? _Lang.arrToMap(vals, offset) : {},
474 
475             //helpers to improve speed and compression
476             none = idIdx[this.IDENT_NONE],
477             all = idIdx[this.IDENT_ALL],
478             theThis = idIdx[this.IDENT_THIS],
479             theForm = idIdx[this.IDENT_FORM];
480 
481         if (none) {
482             //in case of none nothing is returned
483             if ('undefined' != typeof passThrgh.target) {
484                 delete passThrgh.target;
485             }
486             return passThrgh;
487         }
488         if (all) {
489             //in case of all only one value is returned
490             passThrgh[target] = this.IDENT_ALL;
491             return passThrgh;
492         }
493 
494         if (theForm) {
495             //the form is replaced with the proper id but the other
496             //values are not touched
497             vals[theForm - offset] = form.id;
498         }
499         if (theThis && !idIdx[elementId]) {
500             //in case of this, the element id is set
501             vals[theThis - offset] = elementId;
502         }
503 
504         //the final list must be blank separated
505         passThrgh[target] = this._remapNamingContainer(elementId, form, namingContainerId,vals).join(" ");
506         return passThrgh;
507     },
508 
509     /**
510      * in namespaced situations root naming containers must be resolved
511      * ":" absolute searches must be mapped accordingly, the same
512      * goes for absolut searches containing already the root naming container id
513      *
514      * @param issuingElementId the issuing element id
515      * @param form the hosting form of the issiung element id
516      * @param rootNamingContainerId the root naming container id
517      * @param elements a list of element client ids to be processed
518      * @returns {*} the mapped element client ids, which are resolved correctly to their naming containers
519      * @private
520      */
521     _remapNamingContainer: function(issuingElementId, form, rootNamingContainerId, elements) {
522         var SEP = jsf.separatorchar;
523         function remapViewId(toTransform) {
524             var EMPTY_STR = "";
525             var rootNamingContainerPrefix = (rootNamingContainerId.length) ? rootNamingContainerId+SEP : EMPTY_STR;
526             var formClientId = form.id;
527             // nearest parent naming container relative to the form
528             var nearestNamingContainer = formClientId.substring(0, formClientId.lastIndexOf(SEP));
529             var nearestNamingContainerPrefix = (nearestNamingContainer.length) ? nearestNamingContainer + SEP : EMPTY_STR;
530             // absolut search expression, always starts with SEP or the name of the root naming container
531             var hasLeadingSep = toTransform.indexOf(SEP) === 0;
532             var isAbsolutSearchExpr = hasLeadingSep || (rootNamingContainerId.length
533                 && toTransform.indexOf(rootNamingContainerPrefix) == 0);
534             if (isAbsolutSearchExpr) {
535                 //we cut off the leading sep if there is one
536                 toTransform = hasLeadingSep ? toTransform.substring(1) : toTransform;
537                 toTransform = toTransform.indexOf(rootNamingContainerPrefix) == 0 ? toTransform.substring(rootNamingContainerPrefix.length) : toTransform;
538                 //now we prepend either the prefix or "" from the cut-off string to get the final result
539                 return  [rootNamingContainerPrefix, toTransform].join(EMPTY_STR);
540             } else { //relative search according to the javadoc
541                 //we cut off the root naming container id from the form
542                 if (formClientId.indexOf(rootNamingContainerPrefix) == 0) {
543                     formClientId = formClientId.substring(rootNamingContainerPrefix.length);
544                 }
545 
546                 //If prependId = true, the outer form id must be present in the id if same form
547                 var hasPrependId = toTransform.indexOf(formClientId) == 0;
548 
549                 return hasPrependId ?
550                     [rootNamingContainerPrefix, toTransform].join(EMPTY_STR) :
551                     [nearestNamingContainerPrefix, toTransform].join(EMPTY_STR);
552             }
553         }
554 
555         for(var cnt = 0; cnt < elements.length; cnt++) {
556             elements[cnt] = remapViewId(this._Lang.trim(elements[cnt]));
557         }
558 
559         return elements;
560     },
561 
562     addOnError:function (/*function*/errorListener) {
563         /*error handling already done in the assert of the queue*/
564         this._errListeners.enqueue(errorListener);
565     },
566 
567     addOnEvent:function (/*function*/eventListener) {
568         /*error handling already done in the assert of the queue*/
569         this._evtListeners.enqueue(eventListener);
570     },
571 
572     /**
573      * implementation triggering the error chain
574      *
575      * @param {Object} request the request object which comes from the xhr cycle
576      * @param {Object} context (Map) the context object being pushed over the xhr cycle keeping additional metadata
577      * @param {String} name the error name
578      * @param {String} errorName the server error name in case of a server error
579      * @param {String} errorMessage the server error message in case of a server error
580      * @param {String} caller optional caller reference for extended error messages
581      * @param {String} callFunc optional caller Function reference for extended error messages
582      *
583      *  handles the errors, in case of an onError exists within the context the onError is called as local error handler
584      *  the registered error handlers in the queue receiv an error message to be dealt with
585      *  and if the projectStage is at development an alert box is displayed
586      *
587      *  note: we have additional functionality here, via the global config myfaces.config.defaultErrorOutput a function can be provided
588      *  which changes the default output behavior from alert to something else
589      *
590      *
591      */
592     sendError:function sendError(/*Object*/request, /*Object*/ context, /*String*/ name, /*String*/ errorName, /*String*/ errorMessage, caller, callFunc) {
593         var _Lang = myfaces._impl._util._Lang;
594         var UNKNOWN = _Lang.getMessage("UNKNOWN");
595 
596         var eventData = {};
597         //we keep this in a closure because we might reuse it for our errorMessage
598         var malFormedMessage = function () {
599             return (name && name === myfaces._impl.core.Impl.MALFORMEDXML) ? _Lang.getMessage("ERR_MALFORMEDXML") : "";
600         };
601 
602         //by setting unknown values to unknown we can handle cases
603         //better where a simulated context is pushed into the system
604         eventData.type = this.ERROR;
605 
606         eventData.status = name || UNKNOWN;
607         eventData.errorName = errorName || UNKNOWN;
608         eventData.errorMessage = errorMessage || UNKNOWN;
609 
610         try {
611             eventData.source = context.source || UNKNOWN;
612             eventData.responseCode = request.status || UNKNOWN;
613             eventData.responseText = request.responseText || UNKNOWN;
614             eventData.responseXML = request.responseXML || UNKNOWN;
615         } catch (e) {
616             // silently ignore: user can find out by examining the event data
617         }
618         //extended error message only in dev mode
619         if (jsf.getProjectStage() === "Development") {
620             eventData.errorMessage = eventData.errorMessage || "";
621             eventData.errorMessage = (caller) ? eventData.errorMessage + "\nCalling class: " + caller : eventData.errorMessage;
622             eventData.errorMessage = (callFunc) ? eventData.errorMessage + "\n Calling function: " + callFunc : eventData.errorMessage;
623         }
624 
625         /**/
626         if (context["onerror"]) {
627             context.onerror(eventData);
628         }
629 
630         /*now we serve the queue as well*/
631         this._errListeners.broadcastEvent(eventData);
632 
633         if (jsf.getProjectStage() === "Development" && this._errListeners.length() == 0 && !context["onerror"]) {
634             var DIVIDER = "--------------------------------------------------------",
635                     defaultErrorOutput = myfaces._impl.core._Runtime.getGlobalConfig("defaultErrorOutput", (console && console.error) ? console.error : alert),
636                     finalMessage = [],
637             //we remap the function to achieve a better compressability
638                     pushMsg = _Lang.hitch(finalMessage, finalMessage.push);
639 
640             (errorMessage) ? pushMsg(_Lang.getMessage("MSG_ERROR_MESSAGE") + " " + errorMessage + "\n") : null;
641 
642             pushMsg(DIVIDER);
643 
644             (caller) ? pushMsg("Calling class:" + caller) : null;
645             (callFunc) ? pushMsg("Calling function:" + callFunc) : null;
646             (name) ? pushMsg(_Lang.getMessage("MSG_ERROR_NAME") + " " + name) : null;
647             (errorName && name != errorName) ? pushMsg("Server error name: " + errorName) : null;
648 
649             pushMsg(malFormedMessage());
650             pushMsg(DIVIDER);
651             pushMsg(_Lang.getMessage("MSG_DEV_MODE"));
652             defaultErrorOutput(finalMessage.join("\n"));
653         }
654     },
655 
656     /**
657      * sends an event
658      */
659     sendEvent:function sendEvent(/*Object*/request, /*Object*/ context, /*event name*/ name) {
660         var _Lang = myfaces._impl._util._Lang;
661         var eventData = {};
662         var UNKNOWN = _Lang.getMessage("UNKNOWN");
663 
664         eventData.type = this.EVENT;
665 
666         eventData.status = name;
667         eventData.source = context.source;
668 
669         if (name !== this.BEGIN) {
670 
671             try {
672                 //we bypass a problem with ie here, ie throws an exception if no status is given on the xhr object instead of just passing a value
673                 var getValue = function (value, key) {
674                     try {
675                         return value[key]
676                     } catch (e) {
677                         return UNKNOWN;
678                     }
679                 };
680 
681                 eventData.responseCode = getValue(request, "status");
682                 eventData.responseText = getValue(request, "responseText");
683                 eventData.responseXML = getValue(request, "responseXML");
684 
685             } catch (e) {
686                 var impl = myfaces._impl.core._Runtime.getGlobalConfig("jsfAjaxImpl", myfaces._impl.core.Impl);
687                 impl.sendError(request, context, this.CLIENT_ERROR, "ErrorRetrievingResponse",
688                         _Lang.getMessage("ERR_CONSTRUCT", e.toString()));
689 
690                 //client errors are not swallowed
691                 throw e;
692             }
693 
694         }
695 
696         /**/
697         if (context.onevent) {
698             /*calling null to preserve the original scope*/
699             context.onevent.call(null, eventData);
700         }
701 
702         /*now we serve the queue as well*/
703         this._evtListeners.broadcastEvent(eventData);
704     },
705 
706     /**
707      * Spec. 13.3.3
708      * Examining the response markup and updating the DOM tree
709      * @param {XMLHttpRequest} request - the ajax request
710      * @param {Object} context - the ajax context
711      */
712     response:function (request, context) {
713         this._RT.getLocalOrGlobalConfig(context, "responseHandler", myfaces._impl.xhrCore._AjaxResponse).processResponse(request, context);
714     },
715 
716     /**
717      * fetches the separator char from the given script tags
718      *
719      * @return {char} the separator char for the given script tags
720      */
721     getSeparatorChar:function () {
722         if (this._separator) {
723             return this.separatorchar;
724         }
725         var SEPARATOR_CHAR = "separatorchar",
726                 found = false,
727                 getConfig = myfaces._impl.core._Runtime.getGlobalConfig,
728                 scriptTags = document.getElementsByTagName("script");
729         for (var i = 0; i < scriptTags.length && !found; i++) {
730             if (scriptTags[i].src.search(/\/javax\.faces\.resource.*\/jsf\.js.*separator/) != -1) {
731                 found = true;
732                 var result = scriptTags[i].src.match(/separator=([^&;]*)/);
733                 this._separator = decodeURIComponent(result[1]);
734             }
735         }
736         this._separator = getConfig(SEPARATOR_CHAR, this._separator || ":");
737         return this._separator;
738     },
739 
740     /**
741      * @return the project stage also emitted by the server:
742      * it cannot be cached and must be delivered over the server
743      * The value for it comes from the request parameter of the jsf.js script called "stage".
744      */
745     getProjectStage:function () {
746         //since impl is a singleton we only have to do it once at first access
747 
748         if (!this._projectStage) {
749             var PRJ_STAGE = "projectStage",
750                     STG_PROD = "Production",
751 
752                     scriptTags = document.getElementsByTagName("script"),
753                     getConfig = myfaces._impl.core._Runtime.getGlobalConfig,
754                     projectStage = null,
755                     found = false,
756                     allowedProjectStages = {STG_PROD:1, "Development":1, "SystemTest":1, "UnitTest":1};
757 
758             /* run through all script tags and try to find the one that includes jsf.js */
759             for (var i = 0; i < scriptTags.length && !found; i++) {
760                 if (scriptTags[i] && scriptTags[i].src && scriptTags[i].src.search(/\/javax\.faces\.resource\/jsf\.js.*ln=javax\.faces/) != -1) {
761                     var result = scriptTags[i].src.match(/stage=([^&;]*)/);
762                     found = true;
763                     if (result) {
764                         // we found stage=XXX
765                         // return only valid values of ProjectStage
766                         projectStage = (allowedProjectStages[result[1]]) ? result[1] : null;
767 
768                     }
769                     else {
770                         //we found the script, but there was no stage parameter -- Production
771                         //(we also add an override here for testing purposes, the default, however is Production)
772                         projectStage = getConfig(PRJ_STAGE, STG_PROD);
773                     }
774                 }
775             }
776             /* we could not find anything valid --> return the default value */
777             this._projectStage = getConfig(PRJ_STAGE, projectStage || STG_PROD);
778         }
779         return this._projectStage;
780     },
781 
782     /**
783      * implementation of the external chain function
784      * moved into the impl
785      *
786      *  @param {Object} source the source which also becomes
787      * the scope for the calling function (unspecified side behavior)
788      * the spec states here that the source can be any arbitrary code block.
789      * Which means it either is a javascript function directly passed or a code block
790      * which has to be evaluated separately.
791      *
792      * After revisiting the code additional testing against components showed that
793      * the this parameter is only targeted at the component triggering the eval
794      * (event) if a string code block is passed. This is behavior we have to resemble
795      * in our function here as well, I guess.
796      *
797      * @param {Event} event the event object being passed down into the the chain as event origin
798      *   the spec is contradicting here, it on one hand defines event, and on the other
799      *   it says it is optional, after asking, it meant that event must be passed down
800      *   but can be undefined
801      */
802     chain:function (source, event) {
803         var len = arguments.length;
804         var _Lang = this._Lang;
805         var throwErr = function (msgKey) {
806             throw Error("jsf.util.chain: " + _Lang.getMessage(msgKey));
807         };
808         /**
809          * generic error condition checker which raises
810          * an exception if the condition is met
811          * @param assertion
812          * @param message
813          */
814         var errorCondition = function (assertion, message) {
815             if (assertion === true) throwErr(message);
816         };
817         var FUNC = 'function';
818         var ISSTR = _Lang.isString;
819 
820         //the spec is contradicting here, it on one hand defines event, and on the other
821         //it says it is optional, I have cleared this up now
822         //the spec meant the param must be passed down, but can be 'undefined'
823 
824         errorCondition(len < 2, "ERR_EV_OR_UNKNOWN");
825         errorCondition(len < 3 && (FUNC == typeof event || ISSTR(event)), "ERR_EVT_PASS");
826         if (len < 3) {
827             //nothing to be done here, move along
828             return true;
829         }
830         //now we fetch from what is given from the parameter list
831         //we cannot work with splice here in any performant way so we do it the hard way
832         //arguments only are give if not set to undefined even null values!
833 
834         //assertions source either null or set as dom element:
835         errorCondition('undefined' == typeof source, "ERR_SOURCE_DEF_NULL");
836         errorCondition(FUNC == typeof source, "ERR_SOURCE_FUNC");
837         errorCondition(ISSTR(source), "ERR_SOURCE_NOSTR");
838 
839         //assertion if event is a function or a string we already are in our function elements
840         //since event either is undefined, null or a valid event object
841         errorCondition(FUNC == typeof event || ISSTR(event), "ERR_EV_OR_UNKNOWN");
842 
843         for (var cnt = 2; cnt < len; cnt++) {
844             //we do not change the scope of the incoming functions
845             //but we reuse the argument array capabilities of apply
846             var ret;
847 
848             if (FUNC == typeof arguments[cnt]) {
849                 ret = arguments[cnt].call(source, event);
850             } else {
851                 //either a function or a string can be passed in case of a string we have to wrap it into another function
852                 ret = new Function("event", arguments[cnt]).call(source, event);
853             }
854             //now if one function returns false in between we stop the execution of the cycle
855             //here, note we do a strong comparison here to avoid constructs like 'false' or null triggering
856             if (ret === false /*undefined check implicitly done here by using a strong compare*/) {
857                 return false;
858             }
859         }
860         return true;
861     },
862 
863     /**
864      * error handler behavior called internally
865      * and only into the impl it takes care of the
866      * internal message transformation to a myfaces internal error
867      * and then uses the standard send error mechanisms
868      * also a double error logging prevention is done as well
869      *
870      * @param request the request currently being processed
871      * @param context the context affected by this error
872      * @param exception the exception being thrown
873      */
874     stdErrorHandler:function (request, context, exception) {
875         //newer browsers do not allow to hold additional values on native objects like exceptions
876         //we hence capsule it into the request, which is gced automatically
877         //on ie as well, since the stdErrorHandler usually is called between requests
878         //this is a valid approach
879         if (this._threshold == "ERROR") {
880             var mfInternal = exception._mfInternal || {};
881 
882             var finalMsg = [];
883             finalMsg.push(exception.message);
884             this.sendError(request, context,
885                     mfInternal.title || this.CLIENT_ERROR, mfInternal.name || exception.name, finalMsg.join("\n"), mfInternal.caller, mfInternal.callFunc);
886         }
887     },
888 
889     /**
890      * @return the client window id of the current window, if one is given
891      */
892     getClientWindow:function (node) {
893         var fetchWindowIdFromForms = this._Lang.hitch(this, function (forms) {
894             var result_idx = {};
895             var result;
896             var foundCnt = 0;
897             for (var cnt = forms.length - 1; cnt >= 0; cnt--) {
898 
899                 var currentForm = forms[cnt];
900                 var winIdElement = this._Dom.getNamedElementFromForm(currentForm, this.P_WINDOW_ID);
901                 var windowId = (winIdElement) ? winIdElement.value : null;
902 
903                 if (windowId) {
904                     if (foundCnt > 0 && "undefined" == typeof result_idx[windowId]) throw Error("Multiple different windowIds found in document");
905                     result = windowId;
906                     result_idx[windowId] = true;
907                     foundCnt++;
908                 }
909             }
910             return result;
911         });
912 
913         var fetchWindowIdFromURL = function () {
914             var href = window.location.href, windowId = "jfwid";
915             var regex = new RegExp("[\\?&]" + windowId + "=([^&#\\;]*)");
916             var results = regex.exec(href);
917             //initial trial over the url and a regexp
918             if (results != null) return results[1];
919             return null;
920         };
921 
922         //byId ($)
923         var finalNode = (node) ? this._Dom.byId(node) : document.body;
924 
925         var forms = this._Dom.findByTagName(finalNode, "form");
926         var result = fetchWindowIdFromForms(forms);
927         return (null != result) ? result : fetchWindowIdFromURL();
928     },
929 
930     /**
931      * returns the view id from an incoming form
932      * crossport from new codebase
933      * @param form
934      */
935     getViewId: function (form) {
936         var _t = this;
937         var foundViewStates = this._Dom.findAll(form, function(node) {
938             return node.tagName === "INPUT" && node.type === "hidden" && (node.name || "").indexOf(_t.P_VIEWSTATE) !== -1
939         }, true);
940         if(!foundViewStates.length) {
941             return "";
942         }
943         var viewId =  foundViewStates[0].id.split(jsf.separatorchar, 2)[0];
944         var viewStateViewId = viewId.indexOf(this.P_VIEWSTATE) === -1 ? viewId : "";
945         // myfaces specific, we in non portlet environments prepend the viewId
946         // even without being in a naming container, the other components ignore that
947         return form.id.indexOf(viewStateViewId) === 0 ? viewStateViewId : "";
948     }
949 });
950 
951 
952