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:"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},
 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 jsf, 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", "jsf.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         var passThrgh = _Lang.mixMaps({}, options, true, this._BLOCKFILTER);
182 
183         if (event) {
184             passThrgh[this.P_EVT] = event.type;
185         }
186 
187         /**
188          * ajax pass through context with the source
189          * onevent and onerror
190          */
191         var context = {
192             source:elem,
193             onevent:options.onevent,
194             onerror:options.onerror,
195 
196             //TODO move the myfaces part into the _mfInternal part
197             myfaces:options.myfaces,
198             _mfInternal:{}
199         };
200         //additional meta information to speed things up, note internal non jsf
201         //pass through options are stored under _mfInternal in the context
202         var mfInternal = context._mfInternal;
203 
204         /**
205          * fetch the parent form
206          *
207          * note we also add an override possibility here
208          * so that people can use dummy forms and work
209          * with detached objects
210          */
211         var form = (options.myfaces && options.myfaces.form) ?
212                 _Lang.byId(options.myfaces.form) :
213                 this._getForm(elem, event);
214 
215         /**
216          * JSF2.2 client window must be part of the issuing form so it is encoded
217          * automatically in the request
218          */
219         //we set the client window before encoding by a call to jsf.getClientWindow
220         var clientWindow = jsf.getClientWindow(form);
221         //in case someone decorates the getClientWindow we reset the value from
222         //what we are getting
223         if ('undefined' != typeof clientWindow && null != clientWindow) {
224             var formElem = _Dom.getNamedElementFromForm(form, this.P_CLIENTWINDOW);
225             if (formElem) {
226                 //we store the value for later processing during the ajax phase
227                 //job so that we do not get double values
228                 context._mfInternal._clientWindow = jsf.getClientWindow(form);
229             } else {
230                 passThrgh[this.P_CLIENTWINDOW] = jsf.getClientWindow(form);
231             }
232         } /*  spec proposal
233         else {
234             var formElem = _Dom.getNamedElementFromForm(form, this.P_CLIENTWINDOW);
235             if (formElem) {
236                 context._mfInternal._clientWindow = "undefined";
237             } else {
238                 passThrgh[this.P_CLIENTWINDOW] = "undefined";
239             }
240         }
241         */
242 
243         /**
244          * binding contract the jakarta.faces.source must be set
245          */
246         passThrgh[this.P_PARTIAL_SOURCE] = elementId;
247 
248         /**
249          * jakarta.faces.partial.ajax must be set to true
250          */
251         passThrgh[this.P_AJAX] = true;
252 
253         /**
254          * if resetValues is set to true
255          * then we have to set jakarta.faces.resetValues as well
256          * as pass through parameter
257          * the value has to be explicitly true, according to
258          * the specs jsdoc
259          */
260         if(options.resetValues === true) {
261             passThrgh[this.P_RESET_VALUES] = true;
262         }
263 
264         if (options.execute) {
265             /*the options must be a blank delimited list of strings*/
266             /*compliance with Mojarra which automatically adds @this to an execute
267              * the spec rev 2.0a however states, if none is issued nothing at all should be sent down
268              */
269             options.execute = (options.execute.indexOf("@this") == -1) ? options.execute : options.execute;
270 
271             this._transformList(passThrgh, this.P_EXECUTE, options.execute, form, elementId);
272         } else {
273             passThrgh[this.P_EXECUTE] = elementId;
274         }
275 
276         if (options.render) {
277             this._transformList(passThrgh, this.P_RENDER, options.render, form, elementId);
278         }
279 
280         /**
281          * multiple transports upcoming jsf 2.x feature currently allowed
282          * default (no value) xhrQueuedPost
283          *
284          * xhrQueuedPost
285          * xhrPost
286          * xhrGet
287          * xhrQueuedGet
288          * iframePost
289          * iframeQueuedPost
290          *
291          */
292         var transportType = this._getTransportType(context, passThrgh, form);
293 
294         mfInternal["_mfSourceFormId"] = form.id;
295         mfInternal["_mfSourceControlId"] = elementId;
296         mfInternal["_mfTransportType"] = transportType;
297 
298         //mojarra compatibility, mojarra is sending the form id as well
299         //this is not documented behavior but can be determined by running
300         //mojarra under blackbox conditions
301         //i assume it does the same as our formId_submit=1 so leaving it out
302         //wont hurt but for the sake of compatibility we are going to add it
303         passThrgh[form.id] = form.id;
304 
305         /* jsf2.2 only: options.delay || */
306         var delayTimeout = options.delay || this._RT.getLocalOrGlobalConfig(context, "delay", false);
307         if (delayTimeout) {
308             if (this._delayTimeout) {
309                 clearTimeout(this._delayTimeout);
310             }
311             this._delayTimeout = setTimeout(_Lang.hitch(this, function () {
312                 this._transport[transportType](elem, form, context, passThrgh);
313                 this._delayTimeout = null;
314             }), parseInt(delayTimeout));
315         } else {
316             this._transport[transportType](elem, form, context, passThrgh);
317         }
318     },
319 
320     /**
321      * fetches the form in an unprecise manner depending
322      * on an element or event target
323      *
324      * @param elem
325      * @param event
326      */
327     _getForm:function (elem, event) {
328         var _Dom = this._Dom;
329         var _Lang = this._Lang;
330         var form = _Dom.fuzzyFormDetection(elem);
331 
332         if (!form && event) {
333             //in case of no form is given we retry over the issuing event
334             form = _Dom.fuzzyFormDetection(_Lang.getEventTarget(event));
335             if (!form) {
336                 throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getForm", _Lang.getMessage("ERR_FORM"));
337             }
338         } else if (!form) {
339             throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getForm", _Lang.getMessage("ERR_FORM"));
340 
341         }
342         return form;
343     },
344 
345     /**
346      * determines the transport type to be called
347      * for the ajax call
348      *
349      * @param context the context
350      * @param passThrgh  pass through values
351      * @param form the form which issues the request
352      */
353     _getTransportType:function (context, passThrgh, form) {
354         /**
355          * if execute or render exist
356          * we have to pass them down as a blank delimited string representation
357          * of an array of ids!
358          */
359         //for now we turn off the transport auto selection, to enable 2.0 backwards compatibility
360         //on protocol level, the file upload only can be turned on if the auto selection is set to true
361         var getConfig = this._RT.getLocalOrGlobalConfig,
362                 _Lang = this._Lang,
363                 _Dom = this._Dom;
364 
365         var transportAutoSelection = getConfig(context, "transportAutoSelection", true);
366         /*var isMultipart = (transportAutoSelection && _Dom.getAttribute(form, "enctype") == "multipart/form-data") ?
367          _Dom.isMultipartCandidate((!getConfig(context, "pps",false))? form : passThrgh[this.P_EXECUTE]) :
368          false;
369          **/
370         if (!transportAutoSelection) {
371             return getConfig(context, "transportType", "xhrQueuedPost");
372         }
373         var multiPartCandidate = _Dom.isMultipartCandidate((!getConfig(context, "pps", false)) ?
374                 form : passThrgh[this.P_EXECUTE]);
375         var multipartForm = (_Dom.getAttribute(form, "enctype") || "").toLowerCase() == "multipart/form-data";
376         //spec section jsdoc, if we have a multipart candidate in our execute (aka fileupload)
377         //and the form is not multipart then we have to raise an error
378         if (multiPartCandidate && !multipartForm) {
379             throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getTransportType", _Lang.getMessage("ERR_NO_MULTIPART_FORM", "No Multipart form", form.id));
380         }
381         var isMultipart = multiPartCandidate && multipartForm;
382         /**
383          * multiple transports upcoming jsf 2.2 feature currently allowed
384          * default (no value) xhrQueuedPost
385          *
386          * xhrQueuedPost
387          * xhrPost
388          * xhrGet
389          * xhrQueuedGet
390          * iframePost
391          * iframeQueuedPost
392          *
393          */
394         var transportType = (!isMultipart) ?
395                 getConfig(context, "transportType", "xhrQueuedPost") :
396                 getConfig(context, "transportType", "multipartQueuedPost");
397         if (!this._transport[transportType]) {
398             //throw new Error("Transport type " + transportType + " does not exist");
399             throw new Error(_Lang.getMessage("ERR_TRANSPORT", null, transportType));
400         }
401         return transportType;
402 
403     },
404 
405     /**
406      * transforms the list to the expected one
407      * with the proper none all form and this handling
408      * (note we also could use a simple string replace but then
409      * we would have had double entries under some circumstances)
410      *
411      * @param passThrgh
412      * @param target
413      * @param srcStr
414      * @param form
415      * @param elementId
416      */
417     _transformList:function (passThrgh, target, srcStr, form, elementId) {
418         var _Lang = this._Lang;
419         //this is probably the fastest transformation method
420         //it uses an array and an index to position all elements correctly
421         //the offset variable is there to prevent 0 which results in a javascript
422         //false
423         srcStr = this._Lang.trim(srcStr);
424         var offset = 1,
425                 vals = (srcStr) ? srcStr.split(/\s+/) : [],
426                 idIdx = (vals.length) ? _Lang.arrToMap(vals, offset) : {},
427 
428         //helpers to improve speed and compression
429                 none = idIdx[this.IDENT_NONE],
430                 all = idIdx[this.IDENT_ALL],
431                 theThis = idIdx[this.IDENT_THIS],
432                 theForm = idIdx[this.IDENT_FORM];
433 
434         if (none) {
435             //in case of none nothing is returned
436             if ('undefined' != typeof passThrgh.target) {
437                 delete passThrgh.target;
438             }
439             return passThrgh;
440         }
441         if (all) {
442             //in case of all only one value is returned
443             passThrgh[target] = this.IDENT_ALL;
444             return passThrgh;
445         }
446 
447         if (theForm) {
448             //the form is replaced with the proper id but the other
449             //values are not touched
450             vals[theForm - offset] = form.id;
451         }
452         if (theThis && !idIdx[elementId]) {
453             //in case of this, the element id is set
454             vals[theThis - offset] = elementId;
455         }
456 
457         //the final list must be blank separated
458         passThrgh[target] = vals.join(" ");
459         return passThrgh;
460     },
461 
462     addOnError:function (/*function*/errorListener) {
463         /*error handling already done in the assert of the queue*/
464         this._errListeners.enqueue(errorListener);
465     },
466 
467     addOnEvent:function (/*function*/eventListener) {
468         /*error handling already done in the assert of the queue*/
469         this._evtListeners.enqueue(eventListener);
470     },
471 
472     /**
473      * implementation triggering the error chain
474      *
475      * @param {Object} request the request object which comes from the xhr cycle
476      * @param {Object} context (Map) the context object being pushed over the xhr cycle keeping additional metadata
477      * @param {String} name the error name
478      * @param {String} errorName the server error name in case of a server error
479      * @param {String} errorMessage the server error message in case of a server error
480      * @param {String} caller optional caller reference for extended error messages
481      * @param {String} callFunc optional caller Function reference for extended error messages
482      *
483      *  handles the errors, in case of an onError exists within the context the onError is called as local error handler
484      *  the registered error handlers in the queue receiv an error message to be dealt with
485      *  and if the projectStage is at development an alert box is displayed
486      *
487      *  note: we have additional functionality here, via the global config myfaces.config.defaultErrorOutput a function can be provided
488      *  which changes the default output behavior from alert to something else
489      *
490      *
491      */
492     sendError:function sendError(/*Object*/request, /*Object*/ context, /*String*/ name, /*String*/ errorName, /*String*/ errorMessage, caller, callFunc) {
493         var _Lang = myfaces._impl._util._Lang;
494         var UNKNOWN = _Lang.getMessage("UNKNOWN");
495 
496         var eventData = {};
497         //we keep this in a closure because we might reuse it for our errorMessage
498         var malFormedMessage = function () {
499             return (name && name === myfaces._impl.core.Impl.MALFORMEDXML) ? _Lang.getMessage("ERR_MALFORMEDXML") : "";
500         };
501 
502         //by setting unknown values to unknown we can handle cases
503         //better where a simulated context is pushed into the system
504         eventData.type = this.ERROR;
505 
506         eventData.status = name || UNKNOWN;
507         eventData.errorName = errorName || UNKNOWN;
508         eventData.errorMessage = errorMessage || UNKNOWN;
509 
510         try {
511             eventData.source = context.source || UNKNOWN;
512             eventData.responseCode = request.status || UNKNOWN;
513             eventData.responseText = request.responseText || UNKNOWN;
514             eventData.responseXML = request.responseXML || UNKNOWN;
515         } catch (e) {
516             // silently ignore: user can find out by examining the event data
517         }
518         //extended error message only in dev mode
519         if (jsf.getProjectStage() === "Development") {
520             eventData.errorMessage = eventData.errorMessage || "";
521             eventData.errorMessage = (caller) ? eventData.errorMessage + "\nCalling class: " + caller : eventData.errorMessage;
522             eventData.errorMessage = (callFunc) ? eventData.errorMessage + "\n Calling function: " + callFunc : eventData.errorMessage;
523         }
524 
525         /**/
526         if (context["onerror"]) {
527             context.onerror(eventData);
528         }
529 
530         /*now we serve the queue as well*/
531         this._errListeners.broadcastEvent(eventData);
532 
533         if (jsf.getProjectStage() === "Development" && this._errListeners.length() == 0 && !context["onerror"]) {
534             var DIVIDER = "--------------------------------------------------------",
535                     defaultErrorOutput = myfaces._impl.core._Runtime.getGlobalConfig("defaultErrorOutput", alert),
536                     finalMessage = [],
537             //we remap the function to achieve a better compressability
538                     pushMsg = _Lang.hitch(finalMessage, finalMessage.push);
539 
540             (errorMessage) ? pushMsg(_Lang.getMessage("MSG_ERROR_MESSAGE") + " " + errorMessage + "\n") : null;
541 
542             pushMsg(DIVIDER);
543 
544             (caller) ? pushMsg("Calling class:" + caller) : null;
545             (callFunc) ? pushMsg("Calling function:" + callFunc) : null;
546             (name) ? pushMsg(_Lang.getMessage("MSG_ERROR_NAME") + " " + name) : null;
547             (errorName && name != errorName) ? pushMsg("Server error name: " + errorName) : null;
548 
549             pushMsg(malFormedMessage());
550             pushMsg(DIVIDER);
551             pushMsg(_Lang.getMessage("MSG_DEV_MODE"));
552             defaultErrorOutput(finalMessage.join("\n"));
553         }
554     },
555 
556     /**
557      * sends an event
558      */
559     sendEvent:function sendEvent(/*Object*/request, /*Object*/ context, /*event name*/ name) {
560         var _Lang = myfaces._impl._util._Lang;
561         var eventData = {};
562         var UNKNOWN = _Lang.getMessage("UNKNOWN");
563 
564         eventData.type = this.EVENT;
565 
566         eventData.status = name;
567         eventData.source = context.source;
568 
569         if (name !== this.BEGIN) {
570 
571             try {
572                 //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
573                 var getValue = function (value, key) {
574                     try {
575                         return value[key]
576                     } catch (e) {
577                         return UNKNOWN;
578                     }
579                 };
580 
581                 eventData.responseCode = getValue(request, "status");
582                 eventData.responseText = getValue(request, "responseText");
583                 eventData.responseXML = getValue(request, "responseXML");
584 
585             } catch (e) {
586                 var impl = myfaces._impl.core._Runtime.getGlobalConfig("jsfAjaxImpl", myfaces._impl.core.Impl);
587                 impl.sendError(request, context, this.CLIENT_ERROR, "ErrorRetrievingResponse",
588                         _Lang.getMessage("ERR_CONSTRUCT", e.toString()));
589 
590                 //client errors are not swallowed
591                 throw e;
592             }
593 
594         }
595 
596         /**/
597         if (context.onevent) {
598             /*calling null to preserve the original scope*/
599             context.onevent.call(null, eventData);
600         }
601 
602         /*now we serve the queue as well*/
603         this._evtListeners.broadcastEvent(eventData);
604     },
605 
606     /**
607      * Spec. 13.3.3
608      * Examining the response markup and updating the DOM tree
609      * @param {XMLHttpRequest} request - the ajax request
610      * @param {Object} context - the ajax context
611      */
612     response:function (request, context) {
613         this._RT.getLocalOrGlobalConfig(context, "responseHandler", myfaces._impl.xhrCore._AjaxResponse).processResponse(request, context);
614     },
615 
616     /**
617      * fetches the separator char from the given script tags
618      *
619      * @return {char} the separator char for the given script tags
620      */
621     getSeparatorChar:function () {
622         if (this._separator) {
623             return this.separatorchar;
624         }
625         var SEPARATOR_CHAR = "separatorchar",
626                 found = false,
627                 getConfig = myfaces._impl.core._Runtime.getGlobalConfig,
628                 scriptTags = document.getElementsByTagName("script");
629         for (var i = 0; i < scriptTags.length && !found; i++) {
630             if (scriptTags[i].src.search(/\/jakarta\.faces\.resource.*\/jsf\.js.*separator/) != -1) {
631                 found = true;
632                 var result = scriptTags[i].src.match(/separator=([^&;]*)/);
633                 this._separator = decodeURIComponent(result[1]);
634             }
635         }
636         this._separator = getConfig(SEPARATOR_CHAR, this._separator || ":");
637         return this._separator;
638     },
639 
640     /**
641      * @return the project stage also emitted by the server:
642      * it cannot be cached and must be delivered over the server
643      * The value for it comes from the request parameter of the jsf.js script called "stage".
644      */
645     getProjectStage:function () {
646         //since impl is a singleton we only have to do it once at first access
647 
648         if (!this._projectStage) {
649             var PRJ_STAGE = "projectStage",
650                     STG_PROD = "Production",
651 
652                     scriptTags = document.getElementsByTagName("script"),
653                     getConfig = myfaces._impl.core._Runtime.getGlobalConfig,
654                     projectStage = null,
655                     found = false,
656                     allowedProjectStages = {STG_PROD:1, "Development":1, "SystemTest":1, "UnitTest":1};
657 
658             /* run through all script tags and try to find the one that includes jsf.js */
659             for (var i = 0; i < scriptTags.length && !found; i++) {
660                 if (scriptTags[i].src.search(/\/jakarta\.faces\.resource\/jsf\.js.*ln=jakarta\.faces/) != -1) {
661                     var result = scriptTags[i].src.match(/stage=([^&;]*)/);
662                     found = true;
663                     if (result) {
664                         // we found stage=XXX
665                         // return only valid values of ProjectStage
666                         projectStage = (allowedProjectStages[result[1]]) ? result[1] : null;
667 
668                     }
669                     else {
670                         //we found the script, but there was no stage parameter -- Production
671                         //(we also add an override here for testing purposes, the default, however is Production)
672                         projectStage = getConfig(PRJ_STAGE, STG_PROD);
673                     }
674                 }
675             }
676             /* we could not find anything valid --> return the default value */
677             this._projectStage = getConfig(PRJ_STAGE, projectStage || STG_PROD);
678         }
679         return this._projectStage;
680     },
681 
682     /**
683      * implementation of the external chain function
684      * moved into the impl
685      *
686      *  @param {Object} source the source which also becomes
687      * the scope for the calling function (unspecified side behavior)
688      * the spec states here that the source can be any arbitrary code block.
689      * Which means it either is a javascript function directly passed or a code block
690      * which has to be evaluated separately.
691      *
692      * After revisiting the code additional testing against components showed that
693      * the this parameter is only targeted at the component triggering the eval
694      * (event) if a string code block is passed. This is behavior we have to resemble
695      * in our function here as well, I guess.
696      *
697      * @param {Event} event the event object being passed down into the the chain as event origin
698      *   the spec is contradicting here, it on one hand defines event, and on the other
699      *   it says it is optional, after asking, it meant that event must be passed down
700      *   but can be undefined
701      */
702     chain:function (source, event) {
703         var len = arguments.length;
704         var _Lang = this._Lang;
705         var throwErr = function (msgKey) {
706             throw Error("jsf.util.chain: " + _Lang.getMessage(msgKey));
707         };
708         /**
709          * generic error condition checker which raises
710          * an exception if the condition is met
711          * @param assertion
712          * @param message
713          */
714         var errorCondition = function (assertion, message) {
715             if (assertion === true) throwErr(message);
716         };
717         var FUNC = 'function';
718         var ISSTR = _Lang.isString;
719 
720         //the spec is contradicting here, it on one hand defines event, and on the other
721         //it says it is optional, I have cleared this up now
722         //the spec meant the param must be passed down, but can be 'undefined'
723 
724         errorCondition(len < 2, "ERR_EV_OR_UNKNOWN");
725         errorCondition(len < 3 && (FUNC == typeof event || ISSTR(event)), "ERR_EVT_PASS");
726         if (len < 3) {
727             //nothing to be done here, move along
728             return true;
729         }
730         //now we fetch from what is given from the parameter list
731         //we cannot work with splice here in any performant way so we do it the hard way
732         //arguments only are give if not set to undefined even null values!
733 
734         //assertions source either null or set as dom element:
735         errorCondition('undefined' == typeof source, "ERR_SOURCE_DEF_NULL");
736         errorCondition(FUNC == typeof source, "ERR_SOURCE_FUNC");
737         errorCondition(ISSTR(source), "ERR_SOURCE_NOSTR");
738 
739         //assertion if event is a function or a string we already are in our function elements
740         //since event either is undefined, null or a valid event object
741         errorCondition(FUNC == typeof event || ISSTR(event), "ERR_EV_OR_UNKNOWN");
742 
743         for (var cnt = 2; cnt < len; cnt++) {
744             //we do not change the scope of the incoming functions
745             //but we reuse the argument array capabilities of apply
746             var ret;
747 
748             if (FUNC == typeof arguments[cnt]) {
749                 ret = arguments[cnt].call(source, event);
750             } else {
751                 //either a function or a string can be passed in case of a string we have to wrap it into another function
752                 ret = new Function("event", arguments[cnt]).call(source, event);
753             }
754             //now if one function returns false in between we stop the execution of the cycle
755             //here, note we do a strong comparison here to avoid constructs like 'false' or null triggering
756             if (ret === false /*undefined check implicitly done here by using a strong compare*/) {
757                 return false;
758             }
759         }
760         return true;
761     },
762 
763     /**
764      * error handler behavior called internally
765      * and only into the impl it takes care of the
766      * internal message transformation to a myfaces internal error
767      * and then uses the standard send error mechanisms
768      * also a double error logging prevention is done as well
769      *
770      * @param request the request currently being processed
771      * @param context the context affected by this error
772      * @param exception the exception being thrown
773      */
774     stdErrorHandler:function (request, context, exception) {
775         //newer browsers do not allow to hold additional values on native objects like exceptions
776         //we hence capsule it into the request, which is gced automatically
777         //on ie as well, since the stdErrorHandler usually is called between requests
778         //this is a valid approach
779         if (this._threshold == "ERROR") {
780             var mfInternal = exception._mfInternal || {};
781 
782             var finalMsg = [];
783             finalMsg.push(exception.message);
784             this.sendError(request, context,
785                     mfInternal.title || this.CLIENT_ERROR, mfInternal.name || exception.name, finalMsg.join("\n"), mfInternal.caller, mfInternal.callFunc);
786         }
787     },
788 
789     /**
790      * @return the client window id of the current window, if one is given
791      */
792     getClientWindow:function (node) {
793         var fetchWindowIdFromForms = this._Lang.hitch(this, function (forms) {
794             var result_idx = {};
795             var result;
796             var foundCnt = 0;
797             for (var cnt = forms.length - 1; cnt >= 0; cnt--) {
798 
799                 var currentForm = forms[cnt];
800                 var winIdElement = this._Dom.getNamedElementFromForm(currentForm, this.P_WINDOW_ID);
801                 var windowId = (winIdElement) ? winIdElement.value : null;
802 
803                 if (windowId) {
804                     if (foundCnt > 0 && "undefined" == typeof result_idx[windowId]) throw Error("Multiple different windowIds found in document");
805                     result = windowId;
806                     result_idx[windowId] = true;
807                     foundCnt++;
808                 }
809             }
810             return result;
811         });
812 
813         var fetchWindowIdFromURL = function () {
814             var href = window.location.href, windowId = "jfwid";
815             var regex = new RegExp("[\\?&]" + windowId + "=([^&#\\;]*)");
816             var results = regex.exec(href);
817             //initial trial over the url and a regexp
818             if (results != null) return results[1];
819             return null;
820         };
821 
822         //byId ($)
823         var finalNode = (node) ? this._Dom.byId(node) : document.body;
824 
825         var forms = this._Dom.findByTagName(finalNode, "form");
826         var result = fetchWindowIdFromForms(forms);
827         return (null != result) ? result : fetchWindowIdFromURL();
828     }
829 });
830 
831 
832