From 3945b9607bf01bc7b6552fc8a4270e107258b180 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 4 Sep 2025 19:58:16 -0500 Subject: [PATCH] Backportof CSP-related changes --- .../sun/faces/facelets/tag/ui/UIDebug.java | 18 +-- .../sun/faces/renderkit/RenderKitUtils.java | 132 ++++++++++++++---- .../renderkit/html_basic/ButtonRenderer.java | 28 ++-- .../html_basic/CheckboxRenderer.java | 12 +- .../html_basic/CommandLinkRenderer.java | 23 ++- .../html_basic/CommandScriptRenderer.java | 10 +- .../renderkit/html_basic/MenuRenderer.java | 6 +- .../OutcomeTargetButtonRenderer.java | 43 +++--- .../renderkit/html_basic/RadioRenderer.java | 20 +-- .../renderkit/html_basic/SecretRenderer.java | 12 +- .../SelectManyCheckboxListRenderer.java | 15 +- .../renderkit/html_basic/TextRenderer.java | 14 +- .../html_basic/TextareaRenderer.java | 11 +- .../html_basic/WebsocketRenderer.java | 15 +- .../jakarta.faces/faces-uncompressed.js | 33 +++-- .../mojarra/test/issue5576/Issue5576IT.java | 1 + 16 files changed, 235 insertions(+), 158 deletions(-) diff --git a/impl/src/main/java/com/sun/faces/facelets/tag/ui/UIDebug.java b/impl/src/main/java/com/sun/faces/facelets/tag/ui/UIDebug.java index 5a5d436f16..75b69ed1ef 100644 --- a/impl/src/main/java/com/sun/faces/facelets/tag/ui/UIDebug.java +++ b/impl/src/main/java/com/sun/faces/facelets/tag/ui/UIDebug.java @@ -22,16 +22,15 @@ import java.util.List; import java.util.Map; -import com.sun.faces.facelets.util.DevTools; -import com.sun.faces.facelets.util.FastWriter; -import com.sun.faces.renderkit.RenderKitUtils; -import com.sun.faces.renderkit.html_basic.ScriptRenderer; - import jakarta.faces.component.UIComponentBase; import jakarta.faces.context.FacesContext; import jakarta.faces.context.ResponseWriter; import jakarta.servlet.http.HttpServletResponse; +import com.sun.faces.facelets.util.DevTools; +import com.sun.faces.facelets.util.FastWriter; +import com.sun.faces.renderkit.RenderKitUtils; + /** * @author Jacob Hookom */ @@ -103,14 +102,9 @@ public void encodeBegin(FacesContext facesContext) throws IOException { ResponseWriter writer = facesContext.getResponseWriter(); writer.startElement("span", this); writer.writeAttribute("id", getClientId(facesContext), "id"); - writer.startElement("script", this); - - if (!RenderKitUtils.isOutputHtml5Doctype(facesContext)) { - writer.writeAttribute("type", ScriptRenderer.DEFAULT_CONTENT_TYPE, "type"); - } + + RenderKitUtils.renderScript(facesContext, this, null, sb.toString()); - writer.writeText(sb.toString(), this, null); - writer.endElement("script"); writer.endElement("span"); } } diff --git a/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java b/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java index a1bbd394cd..4f4f35d94e 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java +++ b/impl/src/main/java/com/sun/faces/renderkit/RenderKitUtils.java @@ -41,6 +41,7 @@ import com.sun.faces.config.WebConfiguration; import com.sun.faces.el.ELUtils; import com.sun.faces.facelets.util.DevTools; +import com.sun.faces.renderkit.html_basic.ScriptRenderer; import com.sun.faces.util.FacesLogger; import com.sun.faces.util.RequestStateManager; import com.sun.faces.util.Util; @@ -143,6 +144,8 @@ public class RenderKitUtils { */ private static final String ATTRIBUTES_THAT_ARE_SET_KEY = UIComponentBase.class.getName() + ".attributesThatAreSet"; + private static final String BEHAVIOR_EVENT_ATTRIBUTE_PREFIX = "on"; + /** * UIViewRoot attribute key of a boolean value which remembers whether the view will be rendered with a HTML5 doctype. */ @@ -347,10 +350,10 @@ public static void renderPassThruAttributes(FacesContext context, ResponseWriter } } - // Renders the onchange handler for input components. Handles + // Renders the onchange event listener for input components. Handles // chaining together the user-provided onchange handler with // any Behavior scripts. - public static void renderOnchange(FacesContext context, UIComponent component, boolean incExec) throws IOException { + public static void renderOnchangeEventListener(FacesContext context, UIComponent component, boolean incExec) throws IOException { final String handlerName = "onchange"; final Object userHandler = component.getAttributes().get(handlerName); @@ -369,11 +372,11 @@ public static void renderOnchange(FacesContext context, UIComponent component, b params = new LinkedList<>(); params.add(new ClientBehaviorContext.Parameter("incExec", true)); } - renderHandler(context, component, params, handlerName, userHandler, behaviorEventName, null, false, incExec); + renderHandler(context, component, null, params, handlerName, userHandler, behaviorEventName, "change", null, false, incExec, true); } - // Renders onclick handler for SelectRaidio and SelectCheckbox - public static void renderSelectOnclick(FacesContext context, UIComponent component, boolean incExec) throws IOException { + // Renders onclick event listener for SelectRadio and SelectCheckbox + public static void renderSelectOnclickEventListener(FacesContext context, UIComponent component, String clientId, boolean incExec) throws IOException { final String handlerName = "onclick"; final Object userHandler = component.getAttributes().get(handlerName); @@ -392,14 +395,16 @@ public static void renderSelectOnclick(FacesContext context, UIComponent compone params = new LinkedList<>(); params.add(new ClientBehaviorContext.Parameter("incExec", true)); } - renderHandler(context, component, params, handlerName, userHandler, behaviorEventName, null, false, incExec); + renderHandler(context, component, clientId, params, handlerName, userHandler, behaviorEventName, "click", null, false, incExec, true); } - // Renders the onclick handler for command buttons. Handles + // Renders the onclick event listener for command buttons. Handles // chaining together the user-provided onclick handler, any // Behavior scripts, plus the default button submit script. - public static void renderOnclick(FacesContext context, UIComponent component, Collection params, String submitTarget, - boolean needsSubmit) throws IOException { + public static void renderOnclickEventListener(FacesContext context, UIComponent component, + Collection params, + String submitTarget, + boolean needsSubmit) throws IOException { final String handlerName = "onclick"; final Object userHandler = component.getAttributes().get(handlerName); @@ -418,18 +423,17 @@ public static void renderOnclick(FacesContext context, UIComponent component, Co } } - renderHandler(context, component, params, handlerName, userHandler, behaviorEventName, submitTarget, needsSubmit, false); + renderHandler(context, component, null, params, handlerName, userHandler, behaviorEventName, "click", submitTarget, needsSubmit, false, true); } - // Renders the script function for command scripts. + // Renders the script element with the function for command scripts. public static void renderFunction(FacesContext context, UIComponent component, Collection params, String submitTarget) throws IOException { ClientBehaviorContext behaviorContext = ClientBehaviorContext.createClientBehaviorContext(context, component, "action", submitTarget, params); AjaxBehavior behavior = (AjaxBehavior) context.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID); mapAttributes(component, behavior, "execute", "render", "onerror", "onevent", "resetValues"); - - context.getResponseWriter().append(behavior.getScript(behaviorContext)); + renderScript(context, component, null, behavior.getScript(behaviorContext)); } private static void mapAttributes(UIComponent component, AjaxBehavior behavior, String... attributeNames) { @@ -623,7 +627,19 @@ private static void renderPassThruAttributesOptimized(FacesContext context, Resp Attribute attr = knownAttributes[index]; if (isBehaviorEventAttribute(attr, behaviorEventName)) { - renderHandler(context, component, null, name, value, behaviorEventName, null, false, false); + renderHandler(context, component, null, null, name, value, behaviorEventName, behaviorEventName, null, false, false, false); + + renderedBehavior = true; + } else { + writer.writeAttribute(prefixAttribute(name, isXhtml), value, name); + } + } + } + else if (isBehaviorEventAttribute(name)) { + Object value = attrMap.get(name); + if (value != null && shouldRenderAttribute(value)) { + if (name.substring(2).equals(behaviorEventName)) { + renderHandler(context, component, null, null, name, value, behaviorEventName, behaviorEventName, null, false, false, false); renderedBehavior = true; } else { @@ -644,8 +660,8 @@ private static void renderPassThruAttributesOptimized(FacesContext context, Resp Attribute attr = knownAttributes[i]; String[] events = attr.getEvents(); if (events != null && events.length > 0 && behaviorEventName.equals(events[0])) { - - renderHandler(context, component, null, attr.getName(), null, behaviorEventName, null, false, false); + renderHandler(context, component, null, null, attr.getName(), null, behaviorEventName, behaviorEventName, null, false, false, false); + return; } } @@ -683,12 +699,34 @@ private static void renderPassThruAttributesUnoptimized(FacesContext context, Re // If we've got a behavior for this attribute, // we may need to chain scripts together, so use - // renderHandler(). - renderHandler(context, component, null, attrName, value, events[0], null, false, false); + // renderEventListener(). + renderHandler(context, component, null, null, attrName, value, events[0], events[0], null, false, false, false); } } } + private static void renderPassthruAttribute(FacesContext context, ResponseWriter writer, UIComponent component, + Map> behaviors, boolean isXhtml, Map attrMap, String attrName, + String eventName) throws IOException { + boolean hasBehavior = eventName != null && behaviors.containsKey(eventName); + + Object value = attrMap.get(attrName); + + if (value != null && shouldRenderAttribute(value) && !hasBehavior) { + writer.writeAttribute(prefixAttribute(attrName, isXhtml), value, attrName); + } else if (hasBehavior) { + + // If we've got a behavior for this attribute, + // we may need to chain scripts together, so use + // renderEventListener(). + renderHandler(context, component, null, null, attrName, value, eventName, eventName, null, false, false, false); + } + } + + public static boolean isBehaviorEventAttribute(String name) { + return name.startsWith(BEHAVIOR_EVENT_ATTRIBUTE_PREFIX) && name.length() > 2; + } + /** *

* Determines if an attribute should be rendered based on the specified #attributeVal. @@ -1492,7 +1530,7 @@ private static String getSubmitHandler(FacesContext context, UIComponent compone builder.append("')"); if (preventDefault) { - builder.append(";return false"); + builder.append(";event.preventDefault()"); } return builder.toString(); @@ -1530,10 +1568,10 @@ private static String getChainedHandler(FacesContext context, UIComponent compon builder.append(")"); // If we're submitting (either via a behavior, or by rendering - // a submit script), we need to return false to prevent the - // default button/link action. + // a submit script), we need to prevent the + // default button/link action event. if (submitting && ("action".equals(behaviorEventName) || "click".equals(behaviorEventName))) { - builder.append(";return false"); + builder.append(";event.preventDefault()"); } return builder.toString(); @@ -1554,7 +1592,7 @@ private static String getSingleBehaviorHandler(FacesContext context, UIComponent script = getSubmitHandler(context, component, params, submitTarget, preventDefault); } } else if (preventDefault) { - script = script + ";return false"; + script = script + ";event.preventDefault()"; } return script; @@ -1583,15 +1621,16 @@ private static boolean isSubmitting(ClientBehavior behavior) { * @param handlerValue the user-specified value for the handler attribute * @param behaviorEventName the name of the behavior event that corresponds to this handler (eg. "action" or * "mouseover"). + * @param domEventName the name of the DOM event that corresponds to this handler (eg. "click" or + * "change"). * @param needsSubmit indicates whether the mojarra.cljs() "submit" script is required by the component. Most * components do not need this, either because they submit themselves (eg. commandButton), or because they do not * perform submits (eg. non-command components). This flag is mainly here for the commandLink case, where we need to * render the submit script to make the link submit. */ - private static void renderHandler(FacesContext context, UIComponent component, Collection params, String handlerName, - Object handlerValue, String behaviorEventName, String submitTarget, boolean needsSubmit, boolean includeExec) throws IOException { + private static void renderHandler(FacesContext context, UIComponent component, String clientId, Collection params, String handlerName, + Object handlerValue, String behaviorEventName, String domEventName, String submitTarget, boolean needsSubmit, boolean includeExec, boolean asEventListener) throws IOException { - ResponseWriter writer = context.getResponseWriter(); String userHandler = getNonEmptyUserHandler(handlerValue); List behaviors = getClientBehaviors(component, behaviorEventName); @@ -1625,7 +1664,46 @@ private static void renderHandler(FacesContext context, UIComponent component, C assert false; } - writer.writeAttribute(handlerName, handler, null); + if (handler != null) { + if (asEventListener) { + addEventListener(context, component, clientId, domEventName, handler); + } + else { + context.getResponseWriter().writeAttribute(handlerName, handler, null); + } + } + } + + public static void addEventListener(FacesContext context, UIComponent component, String clientId, String domEventName, String function) throws IOException { + StringBuilder script = new StringBuilder("mojarra.ael('") + .append(clientId != null ? clientId : component.getClientId(context)) + .append("','") + .append(domEventName) + .append("',function(event){" + function + "})"); + + if (context.getPartialViewContext().isAjaxRequest()) { + context.getPartialViewContext().getEvalScripts().add(script.toString()); + } + else { + renderScript(context, component, null, script.toString()); + } + } + + public static void renderScript(FacesContext context, UIComponent component, String clientId, String script) throws IOException { + ResponseWriter writer = context.getResponseWriter(); + + writer.startElement("script", component); + + if (clientId != null) { + writer.writeAttribute("id", clientId, "id"); + } + + if (!RenderKitUtils.isOutputHtml5Doctype(context)) { + writer.writeAttribute("type", ScriptRenderer.DEFAULT_CONTENT_TYPE, "type"); + } + + writer.writeText(script, component, null); + writer.endElement("script"); } // Determines the type of handler to render based on what sorts of diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/ButtonRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/ButtonRenderer.java index e851f26c04..fb70584027 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/ButtonRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/ButtonRenderer.java @@ -18,19 +18,12 @@ package com.sun.faces.renderkit.html_basic; -import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.BEHAVIOR_SOURCE_PARAM; - import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.logging.Level; -import com.sun.faces.RIConstants; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UICommand; import jakarta.faces.component.UIComponent; import jakarta.faces.component.behavior.ClientBehavior; @@ -39,6 +32,11 @@ import jakarta.faces.context.ResponseWriter; import jakarta.faces.event.ActionEvent; +import com.sun.faces.RIConstants; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; + /** * ButtonRenderer is a class that renders the current value of UICommand as a Button. */ @@ -98,11 +96,6 @@ public void encodeBegin(FacesContext context, UIComponent component) throws IOEx * when we decide how to do script injection. */ - Collection params = getBehaviorParameters(component); - if (!params.isEmpty() && (type.equals("submit") || type.equals("button"))) { - RenderKitUtils.renderFacesJsIfNecessary(context); - } - String imageSrc = (String) component.getAttributes().get("image"); writer.startElement("input", component); writeIdAttributeIfNecessary(context, writer, component); @@ -134,8 +127,6 @@ else if (writer.getContentType().equals(RIConstants.XHTML_CONTENT_TYPE)) { writer.writeAttribute("class", styleClass, "styleClass"); } - RenderKitUtils.renderOnclick(context, component, params, null, false); - // PENDING(edburns): Prior to i_spec_1111, this element // was rendered unconditionally @@ -155,6 +146,15 @@ public void encodeEnd(FacesContext context, UIComponent component) throws IOExce if (component.getChildCount() > 0) { context.getResponseWriter().endElement("input"); } + + String type = getButtonType(component); + Collection params = getBehaviorParameters(component); + + if (!params.isEmpty() && (type.equals("submit") || type.equals("button"))) { + RenderKitUtils.renderFacesJsIfNecessary(context); + } + + RenderKitUtils.renderOnclickEventListener(context, component, params, null, false); } // --------------------------------------------------------- Private Methods diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/CheckboxRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/CheckboxRenderer.java index 6f064512ee..1c5ff9bf6d 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/CheckboxRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/CheckboxRenderer.java @@ -22,15 +22,15 @@ import java.util.Map; import java.util.logging.Level; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UIComponent; import jakarta.faces.context.FacesContext; import jakarta.faces.context.ResponseWriter; import jakarta.faces.convert.ConverterException; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; + /** * CheckboxRenderer is a class that renders the current value of UISelectBoolean as a checkbox. */ @@ -104,10 +104,10 @@ protected void getEndTextToRender(FacesContext context, UIComponent component, S RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES, getNonOnClickSelectBehaviors(component)); RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); - RenderKitUtils.renderSelectOnclick(context, component, false); - writer.endElement("input"); + RenderKitUtils.renderSelectOnclickEventListener(context, component, null, false); + } // --------------------------------------------------------- Private Methods diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandLinkRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandLinkRenderer.java index 40b40fdb97..927b608f20 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandLinkRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandLinkRenderer.java @@ -24,10 +24,6 @@ import java.util.Map; import java.util.logging.Level; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UICommand; import jakarta.faces.component.UIComponent; import jakarta.faces.component.behavior.ClientBehavior; @@ -36,6 +32,10 @@ import jakarta.faces.context.ResponseWriter; import jakarta.faces.event.ActionEvent; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; + /** * CommandLinkRenderer is a class that renders the current value of UICommand as a HyperLink that * acts like a Button. @@ -158,6 +158,12 @@ protected void renderAsActive(FacesContext context, UIComponent command) throws RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, command); + writeCommonLinkAttributes(writer, command); + + // render the current value as link text. + writeValue(command, writer); + writer.flush(); + String target = (String) command.getAttributes().get("target"); if (target != null) { target = target.trim(); @@ -166,14 +172,7 @@ protected void renderAsActive(FacesContext context, UIComponent command) throws } Collection params = getBehaviorParameters(command); - RenderKitUtils.renderOnclick(context, command, params, target, true); - - writeCommonLinkAttributes(writer, command); - - // render the current value as link text. - writeValue(command, writer); - writer.flush(); - + RenderKitUtils.renderOnclickEventListener(context, command, params, target, true); } // --------------------------------------------------------- Private Methods diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandScriptRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandScriptRenderer.java index 01e3940566..778909a759 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandScriptRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandScriptRenderer.java @@ -22,8 +22,6 @@ import java.util.logging.Level; import java.util.regex.Pattern; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UICommand; import jakarta.faces.component.UIComponent; import jakarta.faces.component.html.HtmlCommandScript; @@ -32,6 +30,8 @@ import jakarta.faces.event.ActionEvent; import jakarta.faces.event.PhaseId; +import com.sun.faces.renderkit.RenderKitUtils; + /** * CommandScriptRenderer is a class that renders the current value of UICommand as a Script that acts * like an Ajax Button. @@ -90,11 +90,6 @@ public void encodeBegin(FacesContext context, UIComponent component) throws IOEx writer.startElement("span", commandScript); writer.writeAttribute("id", clientId, "id"); - writer.startElement("script", commandScript); - - if (!RenderKitUtils.isOutputHtml5Doctype(context)) { - writer.writeAttribute("type", ScriptRenderer.DEFAULT_CONTENT_TYPE, "type"); - } RenderKitUtils.renderFunction(context, component, getBehaviorParameters(commandScript), clientId); } @@ -110,7 +105,6 @@ public void encodeEnd(FacesContext context, UIComponent component) throws IOExce ResponseWriter writer = context.getResponseWriter(); assert writer != null; - writer.endElement("script"); writer.endElement("span"); } diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java index 08e8b2dd23..e2a24a9d96 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java @@ -20,7 +20,7 @@ import static com.sun.faces.RIConstants.NO_VALUE; import static com.sun.faces.renderkit.RenderKitUtils.getSelectItems; -import static com.sun.faces.renderkit.RenderKitUtils.renderOnchange; +import static com.sun.faces.renderkit.RenderKitUtils.renderOnchangeEventListener; import static com.sun.faces.renderkit.RenderKitUtils.renderPassThruAttributes; import static com.sun.faces.renderkit.RenderKitUtils.renderXHTMLStyleBooleanAttributes; import static com.sun.faces.util.MessageUtils.CONVERSION_ERROR_MESSAGE_ID; @@ -664,12 +664,12 @@ protected void renderSelect(FacesContext context, UIComponent component) throws renderPassThruAttributes(context, writer, component, ATTRIBUTES, getNonOnChangeBehaviors(component)); renderXHTMLStyleBooleanAttributes(writer, component); - renderOnchange(context, component, false); - // Now, write the buffered option content writer.write(bufferedWriter.toString()); writer.endElement("select"); + + renderOnchangeEventListener(context, component, false); } protected Integer getSizeAttribute(UIComponent component) { diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutcomeTargetButtonRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutcomeTargetButtonRenderer.java index 6c53763a4e..29c21b7e72 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutcomeTargetButtonRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutcomeTargetButtonRenderer.java @@ -18,6 +18,11 @@ import java.io.IOException; +import jakarta.faces.application.NavigationCase; +import jakarta.faces.component.UIComponent; +import jakarta.faces.context.FacesContext; +import jakarta.faces.context.ResponseWriter; + import com.sun.faces.RIConstants; import com.sun.faces.renderkit.Attribute; import com.sun.faces.renderkit.AttributeManager; @@ -25,11 +30,6 @@ import com.sun.faces.util.MessageUtils; import com.sun.faces.util.Util; -import jakarta.faces.application.NavigationCase; -import jakarta.faces.component.UIComponent; -import jakarta.faces.context.FacesContext; -import jakarta.faces.context.ResponseWriter; - public class OutcomeTargetButtonRenderer extends OutcomeTargetRenderer { private static final Attribute[] ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.OUTCOMETARGETBUTTON); @@ -68,20 +68,6 @@ else if (writer.getContentType().equals(RIConstants.XHTML_CONTENT_TYPE)) { String label = getLabel(component); - if (!Util.componentIsDisabled(component)) { - NavigationCase navCase = getNavigationCase(context, component); - - if (navCase == null) { - // QUESTION should this only be added in development mode? - label += MessageUtils.getExceptionMessageString(MessageUtils.OUTCOME_TARGET_BUTTON_NO_MATCH); - writer.writeAttribute("disabled", "true", "disabled"); - } else { - String hrefVal = getEncodedTargetURL(context, component, navCase); - hrefVal += getFragment(component); - writer.writeAttribute("onclick", getOnclick(component, hrefVal), "onclick"); - } - } - // value should be used even for image type for accessibility (e.g., images disabled in browser) writer.writeAttribute("value", label, "value"); @@ -92,6 +78,16 @@ else if (writer.getContentType().equals(RIConstants.XHTML_CONTENT_TYPE)) { renderPassThruAttributes(context, writer, component, ATTRIBUTES, null); + if (!Util.componentIsDisabled(component)) { + NavigationCase navCase = getNavigationCase(context, component); + + if (navCase == null) { + // QUESTION should this only be added in development mode? + label += MessageUtils.getExceptionMessageString(MessageUtils.OUTCOME_TARGET_BUTTON_NO_MATCH); + writer.writeAttribute("disabled", "true", "disabled"); + } + } + if (component.getChildCount() == 0) { writer.endElement("input"); } @@ -107,6 +103,15 @@ public void encodeEnd(FacesContext context, UIComponent component) throws IOExce context.getResponseWriter().endElement("input"); } + if (!Util.componentIsDisabled(component)) { + NavigationCase navCase = getNavigationCase(context, component); + + if (navCase != null) { + String hrefVal = getEncodedTargetURL(context, component, navCase); + hrefVal += getFragment(component); + RenderKitUtils.addEventListener(context, component, null,"click", getOnclick(component, hrefVal)); + } + } } // ------------------------------------------------------- Protected Methods diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/RadioRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/RadioRenderer.java index b1ed88b0fd..100a3075b4 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/RadioRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/RadioRenderer.java @@ -31,14 +31,6 @@ import java.util.Map; import java.util.logging.Level; -import com.sun.faces.RIConstants; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; -import com.sun.faces.renderkit.SelectItemsIterator; -import com.sun.faces.util.RequestStateManager; -import com.sun.faces.util.Util; - import jakarta.el.ELException; import jakarta.el.ValueExpression; import jakarta.faces.component.UIComponent; @@ -55,6 +47,14 @@ import jakarta.faces.event.PostAddToViewEvent; import jakarta.faces.model.SelectItem; +import com.sun.faces.RIConstants; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; +import com.sun.faces.renderkit.SelectItemsIterator; +import com.sun.faces.util.RequestStateManager; +import com.sun.faces.util.Util; + /** * ReadoRenderer is a class that renders the current value of UISelectOne or UISelectMany * component as a list of radio buttons @@ -314,9 +314,9 @@ protected void renderInput(FacesContext context, ResponseWriter writer, UICompon RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES, getNonOnClickSelectBehaviors(component)); RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); - RenderKitUtils.renderSelectOnclick(context, component, false); - writer.endElement("input"); + + RenderKitUtils.renderSelectOnclickEventListener(context, component, clientId, false); } protected void renderLabel(ResponseWriter writer, UIComponent component, String forClientId, SelectItem curItem, OptionComponentInfo optionInfo) diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/SecretRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/SecretRenderer.java index 4a0e930f79..30959a02b0 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/SecretRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/SecretRenderer.java @@ -20,14 +20,14 @@ import java.io.IOException; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UIComponent; import jakarta.faces.context.FacesContext; import jakarta.faces.context.ResponseWriter; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; + /** * SecretRenderer is a class that renders the current value of UIInput component as a password field. */ @@ -81,8 +81,6 @@ protected void getEndTextToRender(FacesContext context, UIComponent component, S RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES, getNonOnChangeBehaviors(component)); RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); - RenderKitUtils.renderOnchange(context, component, false); - String styleClass; if (null != (styleClass = (String) component.getAttributes().get("styleClass"))) { writer.writeAttribute("class", styleClass, "styleClass"); @@ -90,6 +88,8 @@ protected void getEndTextToRender(FacesContext context, UIComponent component, S writer.endElement("input"); + RenderKitUtils.renderOnchangeEventListener(context, component, false); + } } // end of class SecretRenderer diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/SelectManyCheckboxListRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/SelectManyCheckboxListRenderer.java index 2656959082..91ff7baa61 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/SelectManyCheckboxListRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/SelectManyCheckboxListRenderer.java @@ -24,11 +24,6 @@ import java.io.IOException; import java.util.Iterator; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; -import com.sun.faces.util.RequestStateManager; - import jakarta.faces.component.UIComponent; import jakarta.faces.component.UINamingContainer; import jakarta.faces.component.ValueHolder; @@ -38,6 +33,11 @@ import jakarta.faces.model.SelectItem; import jakarta.faces.model.SelectItemGroup; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; +import com.sun.faces.util.RequestStateManager; + /** * SelectManyCheckboxListRenderer is a class that renders the current value of UISelectMany component * as a list of checkboxes. @@ -275,9 +275,10 @@ protected void renderOption(FacesContext context, UIComponent component, Convert RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); - RenderKitUtils.renderSelectOnclick(context, component, false); - writer.endElement("input"); + + RenderKitUtils.renderSelectOnclickEventListener(context, component, idString, false); + writer.startElement("label", component); writer.writeAttribute("for", idString, "for"); diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextRenderer.java index de230a9786..4e88f7041c 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextRenderer.java @@ -23,11 +23,6 @@ import java.util.HashMap; import java.util.Map; -import com.sun.faces.config.WebConfiguration; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.application.FacesMessage; import jakarta.faces.application.ProjectStage; import jakarta.faces.component.UIComponent; @@ -38,6 +33,11 @@ import jakarta.faces.context.FacesContext; import jakarta.faces.context.ResponseWriter; +import com.sun.faces.config.WebConfiguration; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; + /** * TextRenderer is a class that renders the current value of UIInput or UIOutput * component as a input field or static text. @@ -136,10 +136,10 @@ protected void getEndTextToRender(FacesContext context, UIComponent component, S RenderKitUtils.renderPassThruAttributes(context, writer, component, attributes, getNonOnChangeBehaviors(component)); RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); - RenderKitUtils.renderOnchange(context, component, false); - writer.endElement("input"); + RenderKitUtils.renderOnchangeEventListener(context, component, hasPassthroughAttributes); + } else if (isOutput = component instanceof UIOutput) { if (styleClass != null || style != null || dir != null || lang != null || title != null || hasPassthroughAttributes || (shouldWriteIdAttribute = shouldWriteIdAttribute(component))) { diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextareaRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextareaRenderer.java index a1668035e6..9d304063f3 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextareaRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/TextareaRenderer.java @@ -20,14 +20,14 @@ import java.io.IOException; -import com.sun.faces.renderkit.Attribute; -import com.sun.faces.renderkit.AttributeManager; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UIComponent; import jakarta.faces.context.FacesContext; import jakarta.faces.context.ResponseWriter; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; + /** * TextareaRenderer is a class that renders the current value of UIInput component as a Textarea. */ @@ -63,8 +63,6 @@ protected void getEndTextToRender(FacesContext context, UIComponent component, S RenderKitUtils.renderPassThruAttributes(context, writer, component, ATTRIBUTES); RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); - RenderKitUtils.renderOnchange(context, component, false); - if (component.getAttributes().containsKey("com.sun.faces.addNewLineAtStart") && "true".equalsIgnoreCase((String) component.getAttributes().get("com.sun.faces.addNewLineAtStart"))) { writer.writeText("\n", null); @@ -77,6 +75,7 @@ protected void getEndTextToRender(FacesContext context, UIComponent component, S writer.endElement("textarea"); + RenderKitUtils.renderOnchangeEventListener(context, component, false); } } diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/WebsocketRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/WebsocketRenderer.java index 0d1df9dd31..4e7fc52dbb 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/WebsocketRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/WebsocketRenderer.java @@ -26,21 +26,20 @@ import java.util.Map; import java.util.Map.Entry; -import com.sun.faces.push.WebsocketChannelManager; -import com.sun.faces.push.WebsocketFacesListener; -import com.sun.faces.renderkit.RenderKitUtils; - import jakarta.faces.component.UIComponent; import jakarta.faces.component.UIWebsocket; import jakarta.faces.component.behavior.ClientBehavior; import jakarta.faces.context.FacesContext; -import jakarta.faces.context.ResponseWriter; import jakarta.faces.event.AbortProcessingException; import jakarta.faces.event.ComponentSystemEvent; import jakarta.faces.event.ComponentSystemEventListener; import jakarta.faces.event.ListenerFor; import jakarta.faces.event.PostAddToViewEvent; +import com.sun.faces.push.WebsocketChannelManager; +import com.sun.faces.push.WebsocketFacesListener; +import com.sun.faces.renderkit.RenderKitUtils; + /** * WebsocketRenderer is a class that renders the faces.push.init() script and decodes any client * behaviors triggered by {@link UIWebsocket}. @@ -95,11 +94,7 @@ public void encodeEnd(FacesContext context, UIComponent component) throws IOExce RenderKitUtils.renderFacesJsIfNecessary(context); - ResponseWriter writer = context.getResponseWriter(); - writer.startElement("script", component); - writer.writeAttribute("id", clientId, "id"); - writer.write(String.format(SCRIPT_INIT, clientId, url, channel, functions, behaviors, connected)); - writer.endElement("script"); + RenderKitUtils.renderScript(context, component, clientId, String.format(SCRIPT_INIT, clientId, url, channel, functions, behaviors, connected)); } } diff --git a/impl/src/main/resources/META-INF/resources/jakarta.faces/faces-uncompressed.js b/impl/src/main/resources/META-INF/resources/jakarta.faces/faces-uncompressed.js index be3fbfa48f..b09598e6ed 100644 --- a/impl/src/main/resources/META-INF/resources/jakarta.faces/faces-uncompressed.js +++ b/impl/src/main/resources/META-INF/resources/jakarta.faces/faces-uncompressed.js @@ -646,6 +646,9 @@ if (!((faces && faces.specversion && faces.specversion >= 23000 ) && var src = scriptStr[1].match(findsrc); var scriptLoadedViaUrl = false; + const thisScript = document.querySelector("script[src*='jakarta.faces.resource/faces.js']"); + const nonce = isNotNull(thisScript) ? thisScript.nonce : undefined; + if (!!src && src[1]) { // if this is a file, load it var url = unescapeHTML(src[1]); @@ -658,7 +661,7 @@ if (!((faces && faces.specversion && faces.specversion >= 23000 ) && parserElement.innerHTML = scriptStr[0]; cloneAttributes(scriptNode, parserElement.firstChild); deleteNode(parserElement); - scriptNode.type = 'text/javascript'; + scriptNode.nonce = nonce; scriptNode.src = url; // add the src to the script node scriptNode.onload = scriptNode.onreadystatechange = function(_, abort) { if (abort || !scriptNode.readyState || /loaded|complete/.test(scriptNode.readyState)) { @@ -667,7 +670,7 @@ if (!((faces && faces.specversion && faces.specversion >= 23000 ) && runScript(head, loadedScriptUrls, scripts, index + 1); // Run next script. } }; - head.insertBefore(scriptNode, null); // add it to end of the head (and don't remove it) + head.appendChild(scriptNode); // add it to end of the head (and don't remove it) scriptLoadedViaUrl = true; } } else if (!!scriptStr && scriptStr[2]) { @@ -677,7 +680,7 @@ if (!((faces && faces.specversion && faces.specversion >= 23000 ) && if (!!script) { // create script node var scriptNode = document.createElement('script'); - scriptNode.type = 'text/javascript'; + scriptNode.nonce = nonce; scriptNode.text = script; // add the code to the script node head.appendChild(scriptNode); // add it to the head head.removeChild(scriptNode); // then remove it @@ -1687,20 +1690,17 @@ if (!((faces && faces.specversion && faces.specversion >= 23000 ) && * @param element to eval * @ignore */ - var doEval = function doEval(element) { - var evalText = ''; - var childNodes = element.childNodes; - for (var i = 0; i < childNodes.length; i++) { - evalText += childNodes[i].nodeValue; - } - globalEval(evalText); + const doEval = function doEval(element) { + const script = element ? element.textContent : undefined; + if (script) runScripts([[null, '', script]]); + else console.warn('called doEval with no source code'); }; /** * Ajax Request Queue * @ignore */ - var Queue = new function Queue() { + const Queue = new function Queue() { // Create the internal queue var queue = []; @@ -3749,3 +3749,14 @@ mojarra.l = function l(l) { window.onload = l; } }; + +/** + * Add event listener. + * + * @param id element id + * @param ev event name + * @param fn function + */ +mojarra.ael = function ael(id, ev, fn) { + document.getElementById(id).addEventListener(ev, fn); +}; diff --git a/test/issue5576/src/test/java/org/eclipse/mojarra/test/issue5576/Issue5576IT.java b/test/issue5576/src/test/java/org/eclipse/mojarra/test/issue5576/Issue5576IT.java index 963a73f113..5c07456ee7 100644 --- a/test/issue5576/src/test/java/org/eclipse/mojarra/test/issue5576/Issue5576IT.java +++ b/test/issue5576/src/test/java/org/eclipse/mojarra/test/issue5576/Issue5576IT.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.eclipse.mojarra.test.base.BaseIT; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy;