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