MYFACES-4280 allow behavior scripts as function instead of string
diff --git a/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java b/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java
index ace25c1..9406e33 100755
--- a/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java
+++ b/impl/src/main/java/org/apache/myfaces/config/MyfacesConfig.java
@@ -822,6 +822,19 @@
     public static final String WEBSOCKET_MAX_CONNECTIONS = "org.apache.myfaces.WEBSOCKET_MAX_CONNECTIONS";
     public static final Integer WEBSOCKET_MAX_CONNECTIONS_DEFAULT = 5000;
     
+    
+    /**
+     * Defines if the clientbehavior scripts are passed as string or function to the jsf.util.chain.
+     * "As string" is actually the default behavior of both MyFaces (until 3.0) and Mojarra.
+     * "As function" is quite usefull for CSP as no string needs to be evaluated as function.
+     * 
+     * Our jsf.util.chain supports both of course.
+     */
+    @JSFWebConfigParam(name="org.apache.myfaces.RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING", since="3.0", defaultValue = "false")
+    public static final String RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING = "org.apache.myfaces.RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING";
+    public static final boolean RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING_DEFAULT = false;
+    
+    
     // we need it, applicationImpl not ready probably
     private ProjectStage projectStage = ProjectStage.Production;
     private boolean strictJsf2AllowSlashLibraryName;
@@ -902,6 +915,7 @@
     private boolean resourceCacheLastModified = RESOURCE_CACHE_LAST_MODIFIED_DEFAULT;
     private boolean logWebContextParams = false;
     private int websocketMaxConnections = WEBSOCKET_MAX_CONNECTIONS_DEFAULT;
+    private boolean renderClientBehaviorScriptsAsString = RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING_DEFAULT;
     
     private static final boolean MYFACES_IMPL_AVAILABLE;
     private static final boolean RI_IMPL_AVAILABLE;
@@ -1316,6 +1330,9 @@
         cfg.websocketMaxConnections = getInt(extCtx, WEBSOCKET_MAX_CONNECTIONS,
                 WEBSOCKET_MAX_CONNECTIONS_DEFAULT);
 
+        cfg.renderClientBehaviorScriptsAsString = getBoolean(extCtx, RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING,
+                RENDER_CLIENTBEHAVIOR_SCRIPTS_AS_STRING_DEFAULT);
+
         return cfg;
     }
 
@@ -1781,5 +1798,9 @@
         return websocketMaxConnections;
     }
 
+    public boolean isRenderClientBehaviorScriptsAsString()
+    {
+        return renderClientBehaviorScriptsAsString;
+    }
 }
 
diff --git a/impl/src/main/java/org/apache/myfaces/renderkit/html/base/ClientBehaviorRendererUtils.java b/impl/src/main/java/org/apache/myfaces/renderkit/html/base/ClientBehaviorRendererUtils.java
index 439bda7..3aa334d 100644
--- a/impl/src/main/java/org/apache/myfaces/renderkit/html/base/ClientBehaviorRendererUtils.java
+++ b/impl/src/main/java/org/apache/myfaces/renderkit/html/base/ClientBehaviorRendererUtils.java
@@ -30,12 +30,15 @@
 import javax.faces.component.behavior.ClientBehaviorHint;

 import javax.faces.component.behavior.ClientBehaviorHolder;

 import javax.faces.context.FacesContext;

+import org.apache.myfaces.config.MyfacesConfig;

 import org.apache.myfaces.renderkit.RendererUtils;

 import org.apache.myfaces.renderkit.html.util.JavascriptContext;

 import org.apache.myfaces.util.lang.StringUtils;

 

 public class ClientBehaviorRendererUtils

 {

+    private static final boolean RENDER_AS_STRING = false;

+    

     public static void decodeClientBehaviors(FacesContext facesContext, UIComponent component)

     {

         if (!(component instanceof ClientBehaviorHolder))

@@ -132,6 +135,7 @@
      * renderer default script

      *

      * @param eventName    event name ("onclick" etc...)

+     * @param config       the {@link MyfacesConfig}

      * @param uiComponent  the component which has the attachement (or should have)

      * @param facesContext the facesContext

      * @param params       params map of params which have to be dragged into the request

@@ -139,7 +143,9 @@
      *         an empty string if none is present

      */

     private static boolean getClientBehaviorScript(FacesContext facesContext,

-            UIComponent uiComponent, String sourceId, String eventName,

+            MyfacesConfig config,

+            UIComponent uiComponent,

+            String sourceId, String eventName,

             Map<String, List<ClientBehavior>> clientBehaviors,

             JavascriptContext target,

             Collection<ClientBehaviorContext.Parameter> params)

@@ -176,7 +182,7 @@
             {

                 ClientBehavior clientBehavior = attachedEventBehaviors.get(i);

                 submitting = appendClientBehaviourScript(target, context, 

-                        submitting, i < (size -1), clientBehavior);   

+                        submitting, i < (size -1), clientBehavior, config);   

             }

         }

         else 

@@ -186,7 +192,7 @@
             {

                 ClientBehavior clientBehavior = clientIterator.next();

                 submitting = appendClientBehaviourScript(target, context, submitting, 

-                        clientIterator.hasNext(), clientBehavior);

+                        clientIterator.hasNext(), clientBehavior, config);

             }

         }

         

@@ -194,16 +200,15 @@
     }

 

     private static boolean appendClientBehaviourScript(JavascriptContext target, ClientBehaviorContext context, 

-            boolean submitting, boolean hasNext, ClientBehavior clientBehavior)

+            boolean submitting, boolean hasNext, ClientBehavior clientBehavior, MyfacesConfig config)

     {

         String script = clientBehavior.getScript(context);

+

         // The script _can_ be null, and in fact is for <f:ajax disabled="true" />

         if (script != null)

         {

-            //either strings or functions, but I assume string is more appropriate 

-            //since it allows access to the

-            //origin as this!

-            target.append('\'' + escapeJavaScriptForChain(script) + '\'');

+            addFunction(script, target, config);

+

             if (hasNext)

             {

                 target.append(", ");

@@ -221,7 +226,8 @@
     }

 

     public static String buildBehaviorChain(FacesContext facesContext,

-            UIComponent uiComponent, String eventName,

+            UIComponent uiComponent,

+            String eventName,

             Collection<ClientBehaviorContext.Parameter> params,

             Map<String, List<ClientBehavior>> clientBehaviors,

             String userEventCode, String serverEventCode)

@@ -232,23 +238,24 @@
     }

 

     public static String buildBehaviorChain(FacesContext facesContext,

-            UIComponent uiComponent, String sourceId, String eventName,

+            UIComponent uiComponent,

+            String sourceId, String eventName,

             Collection<ClientBehaviorContext.Parameter> params,

             Map<String, List<ClientBehavior>> clientBehaviors,

             String userEventCode, String serverEventCode)

     {

+        MyfacesConfig config = MyfacesConfig.getCurrentInstance(facesContext);

+

         List<String> functions = new ArrayList<>(3);

         if (StringUtils.isNotBlank(userEventCode))

         {

-            // escape every ' in the user event code since it will

-            // be a string attribute of jsf.util.chain

-            functions.add('\'' + escapeJavaScriptForChain(userEventCode) + '\'');

+            addFunction(userEventCode, functions, config);

         }

         

         JavascriptContext chainContext = new JavascriptContext();

         

         JavascriptContext behaviorContext = new JavascriptContext();

-        getClientBehaviorScript(facesContext, uiComponent, sourceId,

+        getClientBehaviorScript(facesContext, config, uiComponent, sourceId,

                 eventName, clientBehaviors, behaviorContext, params);

         

         String behaviorScript = behaviorContext.toString();

@@ -258,7 +265,7 @@
         }

         if (StringUtils.isNotBlank(serverEventCode))

         {

-            functions.add('\'' + escapeJavaScriptForChain(serverEventCode) + '\'');

+            addFunction(serverEventCode, functions, config);

         }

 

         // It's possible that there are no behaviors to render.

@@ -292,19 +299,6 @@
         return chainContext.toString();

     }

 

-    /**

-     * @param facesContext

-     * @param uiComponent

-     * @param eventName1

-     * @param params1

-     * @param eventName2

-     * @param params2

-     * @param clientBehaviors

-     * @param userEventCode

-     * @param serverEventCode

-     

-     * @return

-     */

     public static String buildBehaviorChain(FacesContext facesContext,

             UIComponent uiComponent,

             String eventName1,

@@ -315,7 +309,8 @@
             String userEventCode,

             String serverEventCode)

     {

-        return buildBehaviorChain(facesContext, uiComponent, null,

+        return buildBehaviorChain(facesContext,

+                uiComponent, null,

                 eventName1, params1,

                 eventName2, params2,

                 clientBehaviors, userEventCode, serverEventCode);

@@ -332,21 +327,23 @@
             String userEventCode,

             String serverEventCode)

     {

+        MyfacesConfig config = MyfacesConfig.getCurrentInstance(facesContext);

+        

         List<String> functions = new ArrayList<>(3);

         if (StringUtils.isNotBlank(userEventCode))

         {

-            functions.add('\'' + escapeJavaScriptForChain(userEventCode) + '\'');

+            addFunction(userEventCode, functions, config);

         }

 

         JavascriptContext chainContext = new JavascriptContext();

         

         JavascriptContext behaviorContext1 = new JavascriptContext();

-        boolean submitting1 = getClientBehaviorScript(facesContext,

+        boolean submitting1 = getClientBehaviorScript(facesContext, config,

                 uiComponent, sourceId, eventName1, clientBehaviors,

                 behaviorContext1, params1);

 

         JavascriptContext behaviorContext2 = new JavascriptContext();

-        boolean submitting2 = getClientBehaviorScript(facesContext,

+        boolean submitting2 = getClientBehaviorScript(facesContext, config,

                 uiComponent, sourceId, eventName2, clientBehaviors,

                 behaviorContext2, params2);

 

@@ -367,7 +364,7 @@
 

         if (StringUtils.isNotBlank(serverEventCode))

         {

-            functions.add('\'' + escapeJavaScriptForChain(serverEventCode) + '\'');

+            addFunction(serverEventCode, functions, config);

         }

         

         // It's possible that there are no behaviors to render.  For example, if we have

@@ -453,4 +450,43 @@
             return out.toString();

         }

     }

+    

+    private static void addFunction(String function, List<String> functions, MyfacesConfig config)

+    {

+        if (StringUtils.isNotBlank(function))

+        {

+            // either strings or functions are allowed

+            if (config.isRenderClientBehaviorScriptsAsString())

+            {

+                // escape every ' in the user event code since it will be a string attribute of jsf.util.chain

+                functions.add('\'' + escapeJavaScriptForChain(function) + '\'');

+            }

+            else

+            {

+                functions.add("function(event){" + function + "}");

+            }

+        }

+    }

+    

+    private static void addFunction(String function, JavascriptContext target, MyfacesConfig config)

+    {

+        if (StringUtils.isNotBlank(function))

+        {

+            // either strings or functions are allowed

+            if (config.isRenderClientBehaviorScriptsAsString())

+            {

+                // escape every ' in the user event code since it will be a string attribute of jsf.util.chain

+                target.append('\'');

+                target.append(escapeJavaScriptForChain(function));

+                target.append('\'');

+            }

+            else

+            {

+                target.append("function(event){");

+                target.append(function);

+                target.append('}');

+            }

+        }

+    }

+

 }