You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ta...@apache.org on 2026/03/26 13:38:13 UTC

(myfaces) branch main updated: MYFACES-4589 / MYFACES-4577 / MYFACES-4585: improve facelets compatibility

This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/myfaces.git


The following commit(s) were added to refs/heads/main by this push:
     new 2071acdd2 MYFACES-4589 / MYFACES-4577 / MYFACES-4585: improve facelets compatibility
2071acdd2 is described below

commit 2071acdd248eca82fd1f2a785b43cfb61764484f
Author: tandraschko <ta...@apache.org>
AuthorDate: Thu Mar 26 14:38:01 2026 +0100

    MYFACES-4589 / MYFACES-4577 / MYFACES-4585: improve facelets compatibility
---
 .../facelets/el/TaglibAttributeVariableMapper.java | 48 +++++++++++++
 .../view/facelets/el/VariableMapperWrapper.java    |  2 +-
 .../view/facelets/impl/DefaultFaceletContext.java  | 34 ++++++++-
 .../view/facelets/impl/TemplateContextImpl.java    | 44 ++++++++++++
 .../view/facelets/tag/LegacyUserTagHandler.java    |  5 +-
 .../myfaces/view/facelets/tag/UserTagHandler.java  | 29 +++++++-
 .../CompositeComponentResourceTagHandler.java      |  7 +-
 .../view/facelets/tag/ui/CompositionHandler.java   |  8 +++
 .../view/facelets/tag/ui/IncludeHandler.java       |  4 +-
 ...gHandlerCompositeBeanShadowing4589TestCase.java | 69 +++++++++++++++++++
 .../tag/UserTagHandlerVariableMapperTestCase.java  | 80 ++++++++++++++++++++++
 .../tag/ui/IncludeHandler4577TestCase.java         | 70 +++++++++++++++++++
 .../facelets/tag/resources/components/cc4589.xhtml | 17 +++++
 .../tag/ui/myfaces4577_header_fragment.xhtml       | 27 ++++++++
 .../facelets/tag/ui/myfaces4577_header_shell.xhtml | 28 ++++++++
 .../view/facelets/tag/ui/myfaces4577_main.xhtml    | 34 +++++++++
 .../facelets/tag/ui/myfaces4577_template.xhtml     | 30 ++++++++
 .../myfaces/view/facelets/tag/user4585.taglib.xml  | 31 +++++++++
 .../myfaces/view/facelets/tag/user4585_main.xhtml  | 31 +++++++++
 .../myfaces/view/facelets/tag/user4585_panel.xhtml | 25 +++++++
 .../myfaces/view/facelets/tag/user4589.taglib.xml  | 31 +++++++++
 .../myfaces/view/facelets/tag/user4589_main.xhtml  | 20 ++++++
 .../myfaces/view/facelets/tag/user4589_panel.xhtml | 23 +++++++
 23 files changed, 688 insertions(+), 9 deletions(-)

diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/el/TaglibAttributeVariableMapper.java b/impl/src/main/java/org/apache/myfaces/view/facelets/el/TaglibAttributeVariableMapper.java
new file mode 100644
index 000000000..b774ac6a6
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/el/TaglibAttributeVariableMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.view.facelets.el;
+
+import jakarta.el.VariableMapper;
+
+/**
+ * Marks VariableMapper layers that bind Facelets taglib attribute short names
+ * ({@link org.apache.myfaces.view.facelets.tag.UserTagHandler}). Such bindings must not
+ * apply inside nested composite component implementations (MYFACES-4589), where identifiers
+ * like {@code #{color}} must resolve to managed beans (e.g. CDI) instead of shadowing
+ * ancestor taglib attributes named {@code color}.
+ */
+public final class TaglibAttributeVariableMapper extends VariableMapperWrapper
+{
+    public TaglibAttributeVariableMapper(VariableMapper orig)
+    {
+        super(orig);
+    }
+
+    /**
+     * Removes all taglib attribute mapper layers from the top of the chain.
+     */
+    public static VariableMapper unwrapTaglibAttributeScopes(VariableMapper mapper)
+    {
+        while (mapper instanceof TaglibAttributeVariableMapper)
+        {
+            mapper = ((TaglibAttributeVariableMapper) mapper).getWrapped();
+        }
+        return mapper;
+    }
+}
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/el/VariableMapperWrapper.java b/impl/src/main/java/org/apache/myfaces/view/facelets/el/VariableMapperWrapper.java
index da5d57aa6..1cf75581f 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/el/VariableMapperWrapper.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/el/VariableMapperWrapper.java
@@ -34,7 +34,7 @@
  * @author Jacob Hookom
  * @version $Id$
  */
-public final class VariableMapperWrapper extends VariableMapperBase implements FacesWrapper<VariableMapper>
+public class VariableMapperWrapper extends VariableMapperBase implements FacesWrapper<VariableMapper>
 {
     private final VariableMapper _target;
     private final VariableMapperBase _targetBase;
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/impl/DefaultFaceletContext.java b/impl/src/main/java/org/apache/myfaces/view/facelets/impl/DefaultFaceletContext.java
index f25598775..78546e86a 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/impl/DefaultFaceletContext.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/impl/DefaultFaceletContext.java
@@ -420,8 +420,38 @@ public void extendClient(final TemplateClient client)
     public boolean includeDefinition(UIComponent parent, String name)
             throws IOException, FaceletException, FacesException, ELException
     {
-        return _isolatedTemplateContext.get(_currentTemplateContext).includeDefinition(
-                this, this._facelet, parent, name);
+        // Walk template contexts from innermost outward so ui:insert inside ui:include can
+        // resolve ui:define from an enclosing composition (MYFACES-4577). Stop when a
+        // composite-component layer is reached: outer Facelets definitions must not apply
+        // inside composite implementation (TemplateContext javadoc).
+        int i = _currentTemplateContext;
+        while (i >= 0)
+        {
+            TemplateContext tc = _isolatedTemplateContext.get(i);
+            if (tc.includeDefinition(this, this._facelet, parent, name))
+            {
+                return true;
+            }
+            if (tc.getCompositeComponentClient() != null)
+            {
+                break;
+            }
+            // If this layer already has a templating ui:composition (template="..."), do not
+            // merge outer template clients: inner ui:insert must use default body or inner
+            // defines only (multilevel_template_* tests).
+            if (i > 0 && ((TemplateContextImpl) tc).hasTemplatingCompositionExtended())
+            {
+                break;
+            }
+            // Only cross this boundary for ui:include isolation layers (MYFACES-4577). Nested
+            // user-tag or other TemplateContext pushes must not inherit outer ui:define names.
+            if (i > 0 && !((TemplateContextImpl) tc).isIncludeIsolationLayer())
+            {
+                break;
+            }
+            i--;
+        }
+        return false;
     }
 
     @Override
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/impl/TemplateContextImpl.java b/impl/src/main/java/org/apache/myfaces/view/facelets/impl/TemplateContextImpl.java
index 4e346f6f3..c60ad68ea 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/impl/TemplateContextImpl.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/impl/TemplateContextImpl.java
@@ -44,6 +44,7 @@
 import org.apache.myfaces.view.facelets.TemplateClient;
 import org.apache.myfaces.view.facelets.TemplateContext;
 import org.apache.myfaces.view.facelets.TemplateManager;
+import org.apache.myfaces.view.facelets.tag.ui.CompositionHandler;
 
 /**
  * 
@@ -75,9 +76,47 @@ public class TemplateContextImpl extends TemplateContext
     
     private final TemplateClientKnownParameterMap _templateClientKnownParameterMap;
 
+    /**
+     * True for layers pushed by {@code ui:include} to isolate params; used so
+     * {@link org.apache.myfaces.view.facelets.impl.DefaultFaceletContext#includeDefinition}
+     * only merges outer template clients across this boundary (MYFACES-4577), not across
+     * user-tag or other {@link org.apache.myfaces.view.facelets.AbstractFaceletContext#pushTemplateContext}
+     * uses (nested taglib tests).
+     */
+    private final boolean _includeIsolationLayer;
+
+    /**
+     * @return true if any client is a templating {@link CompositionHandler} (template attribute set)
+     */
+    boolean hasTemplatingCompositionExtended()
+    {
+        for (TemplateManagerImpl tm : _clients)
+        {
+            if (tm.isTemplatingComposition())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean isIncludeIsolationLayer()
+    {
+        return _includeIsolationLayer;
+    }
+
     public TemplateContextImpl()
+    {
+        this(false);
+    }
+
+    /**
+     * @param includeIsolationLayer {@code true} when this context is pushed by {@code ui:include}
+     */
+    public TemplateContextImpl(boolean includeIsolationLayer)
     {
         super();
+        _includeIsolationLayer = includeIsolationLayer;
         _clients = new ArrayList<>(4);
         // Parameters registered using ui:param now are bound to template manager instances, because
         // it should follow the same rules as template clients registered here. For example, to resolve
@@ -254,6 +293,11 @@ public boolean isRoot()
         {
             return this._root;
         }
+
+        boolean isTemplatingComposition()
+        {
+            return _target instanceof CompositionHandler && ((CompositionHandler) _target).isTemplating();
+        }
     }
     
     
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/LegacyUserTagHandler.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/LegacyUserTagHandler.java
index a350be213..d4791943b 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/LegacyUserTagHandler.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/LegacyUserTagHandler.java
@@ -38,7 +38,7 @@
 import jakarta.faces.view.facelets.TagHandler;
 import org.apache.myfaces.view.facelets.AbstractFaceletContext;
 import org.apache.myfaces.view.facelets.TemplateClient;
-import org.apache.myfaces.view.facelets.el.VariableMapperWrapper;
+import org.apache.myfaces.view.facelets.el.TaglibAttributeVariableMapper;
 import org.apache.myfaces.view.facelets.tag.ui.DefineHandler;
 
 /**
@@ -111,7 +111,8 @@ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, Fa
             // setup a variable map
             if (this._vars.length > 0)
             {
-                VariableMapper varMapper = new VariableMapperWrapper(orig);
+                VariableMapper varMapper = new TaglibAttributeVariableMapper(
+                        TaglibAttributeVariableMapper.unwrapTaglibAttributeScopes(orig));
                 for (int i = 0; i < this._vars.length; i++)
                 {
                     varMapper.setVariable(names[i], values[i]);
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/UserTagHandler.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/UserTagHandler.java
index 56470762f..995965c7d 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/UserTagHandler.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/UserTagHandler.java
@@ -27,6 +27,7 @@
 
 import jakarta.el.ELException;
 import jakarta.el.ValueExpression;
+import jakarta.el.VariableMapper;
 import jakarta.faces.FacesException;
 import jakarta.faces.component.UIComponent;
 import jakarta.faces.view.facelets.FaceletContext;
@@ -42,6 +43,7 @@
 import org.apache.myfaces.view.facelets.TemplateClient;
 import org.apache.myfaces.view.facelets.TemplateContext;
 import org.apache.myfaces.view.facelets.el.FaceletStateValueExpression;
+import org.apache.myfaces.view.facelets.el.TaglibAttributeVariableMapper;
 import org.apache.myfaces.view.facelets.impl.TemplateContextImpl;
 import org.apache.myfaces.view.facelets.tag.faces.ComponentSupport;
 import org.apache.myfaces.view.facelets.tag.faces.FaceletState;
@@ -96,6 +98,7 @@ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, Fa
             ELException
     {
         AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
+        VariableMapper orig = ctx.getVariableMapper();
         // eval include
         try
         {
@@ -120,7 +123,9 @@ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, Fa
             {
                 if (this._vars.length > 0)
                 {
-                    if (ELExpressionCacheMode.alwaysRecompile.equals(actx.getELExpressionCacheMode()))
+                    boolean alwaysRecompile =
+                            ELExpressionCacheMode.alwaysRecompile.equals(actx.getELExpressionCacheMode());
+                    if (alwaysRecompile)
                     {
                         FaceletState faceletState = ComponentSupport.getFaceletState(ctx, parent, true);
                         for (int i = 0; i < this._vars.length; i++)
@@ -137,6 +142,24 @@ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, Fa
                             ((AbstractFaceletContext) ctx).getTemplateContext().setParameter(names[i], values[i]);
                         }
                     }
+
+                    // MYFACES-4585: same taglib names must be on VariableMapper (as LegacyUserTagHandler does),
+                    // so EL evaluated outside TemplateContext-only paths resolves correctly (e.g. nested components).
+                    // MYFACES-4589 / nested taglib: do not delegate to ancestor user-tag attribute bindings.
+                    VariableMapper varMapper = new TaglibAttributeVariableMapper(
+                            TaglibAttributeVariableMapper.unwrapTaglibAttributeScopes(orig));
+                    for (int i = 0; i < this._vars.length; i++)
+                    {
+                        if (alwaysRecompile)
+                        {
+                            varMapper.setVariable(names[i], new FaceletStateValueExpression(uniqueId, names[i]));
+                        }
+                        else
+                        {
+                            varMapper.setVariable(names[i], values[i]);
+                        }
+                    }
+                    ctx.setVariableMapper(varMapper);
                 }
 
                 // Disable caching always, even in 'always' mode
@@ -150,6 +173,10 @@ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, Fa
             finally
             {
                 fcc.endComponentUniqueIdSection();
+                if (this._vars.length > 0)
+                {
+                    ctx.setVariableMapper(orig);
+                }
             }
         }
         catch (FileNotFoundException e)
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentResourceTagHandler.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentResourceTagHandler.java
index 5fcb5abe7..ed914b7c8 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentResourceTagHandler.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/composite/CompositeComponentResourceTagHandler.java
@@ -59,6 +59,7 @@
 import org.apache.myfaces.view.facelets.FaceletCompositionContext;
 import org.apache.myfaces.view.facelets.TemplateClient;
 import org.apache.myfaces.view.facelets.TemplateContext;
+import org.apache.myfaces.view.facelets.el.TaglibAttributeVariableMapper;
 import org.apache.myfaces.view.facelets.el.VariableMapperWrapper;
 import org.apache.myfaces.view.facelets.tag.ComponentContainerHandler;
 import org.apache.myfaces.view.facelets.tag.TagHandlerUtils;
@@ -445,7 +446,11 @@ protected void applyCompositeComponentFacelet(FaceletContext faceletContext, UIC
         VariableMapper orig = faceletContext.getVariableMapper();
         try
         {
-            faceletContext.setVariableMapper(new VariableMapperWrapper(orig));
+            // MYFACES-4589: taglib UserTagHandler binds attribute short names on VariableMapper
+            // (MYFACES-4585). Those bindings must not shadow managed beans inside this composite
+            // implementation (e.g. #{color.blue} vs ancestor taglib attribute "color").
+            VariableMapper forComposite = TaglibAttributeVariableMapper.unwrapTaglibAttributeScopes(orig);
+            faceletContext.setVariableMapper(new VariableMapperWrapper(forComposite));
             actx.pushCompositeComponentClient(this);
             Resource resourceForCurrentView = faceletContext.getFacesContext().getApplication().
                 getResourceHandler().createResource(_resource.getResourceName(), _resource.getLibraryName());
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/CompositionHandler.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/CompositionHandler.java
index 43c971624..0060881c5 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/CompositionHandler.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/CompositionHandler.java
@@ -54,6 +54,14 @@ public final class CompositionHandler extends TagHandler implements TemplateClie
 
     public final static String NAME = "composition";
 
+    /**
+     * @return true if this tag uses a template attribute (templating composition)
+     */
+    public boolean isTemplating()
+    {
+        return _template != null;
+    }
+
     /**
      * The resolvable URI of the template to use. The content within the composition tag will 
      * be used in populating the template specified.
diff --git a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler.java b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler.java
index e2d26669a..8f988178c 100644
--- a/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler.java
+++ b/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler.java
@@ -199,7 +199,7 @@ else if (_params != null)
                             values[i] = _params[i].getValue(ctx);
                         }
                         
-                        actx.pushTemplateContext(new TemplateContextImpl());
+                        actx.pushTemplateContext(new TemplateContextImpl(true));
                         
                         for (int i = 0; i < _params.length; i++)
                         {
@@ -208,7 +208,7 @@ else if (_params != null)
                     }
                     else
                     {
-                        actx.pushTemplateContext(new TemplateContextImpl());
+                        actx.pushTemplateContext(new TemplateContextImpl(true));
                     }
                     if (url == null)
                     {
diff --git a/impl/src/test/java/org/apache/myfaces/view/facelets/tag/UserTagHandlerCompositeBeanShadowing4589TestCase.java b/impl/src/test/java/org/apache/myfaces/view/facelets/tag/UserTagHandlerCompositeBeanShadowing4589TestCase.java
new file mode 100644
index 000000000..37fbe1b60
--- /dev/null
+++ b/impl/src/test/java/org/apache/myfaces/view/facelets/tag/UserTagHandlerCompositeBeanShadowing4589TestCase.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.view.facelets.tag;
+
+import jakarta.el.ExpressionFactory;
+import jakarta.faces.application.ViewHandler;
+import jakarta.faces.component.UIViewRoot;
+
+import org.apache.myfaces.view.facelets.AbstractFaceletTestCase;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * MYFACES-4589: ancestor Facelets taglib attribute short names (e.g. {@code color}) must not
+ * shadow managed beans with the same identifier inside nested composite implementations.
+ */
+public class UserTagHandlerCompositeBeanShadowing4589TestCase extends AbstractFaceletTestCase
+{
+
+    @Override
+    protected void setUpServletObjects() throws Exception
+    {
+        super.setUpServletObjects();
+        servletContext.addInitParameter(ViewHandler.FACELETS_LIBRARIES_PARAM_NAME, "/user4589.taglib.xml");
+    }
+
+    @Override
+    protected ExpressionFactory createExpressionFactory()
+    {
+        return new org.apache.el.ExpressionFactoryImpl();
+    }
+
+    @Test
+    public void testBeanNotShadowedByTaglibAttributeInNestedComposite4589() throws Exception
+    {
+        facesContext.getExternalContext().getRequestMap().put("color", new ColorBean());
+
+        UIViewRoot root = facesContext.getViewRoot();
+        vdl.buildView(facesContext, root, "user4589_main.xhtml");
+
+        String out = render(root);
+        Assertions.assertTrue(out.contains("MYFACES4589_OK"),
+                "expected #{color.blue} to resolve bean, not taglib attribute: " + out);
+    }
+
+    public static class ColorBean
+    {
+        public String getBlue()
+        {
+            return "MYFACES4589_OK";
+        }
+    }
+}
diff --git a/impl/src/test/java/org/apache/myfaces/view/facelets/tag/UserTagHandlerVariableMapperTestCase.java b/impl/src/test/java/org/apache/myfaces/view/facelets/tag/UserTagHandlerVariableMapperTestCase.java
new file mode 100644
index 000000000..be316c9c0
--- /dev/null
+++ b/impl/src/test/java/org/apache/myfaces/view/facelets/tag/UserTagHandlerVariableMapperTestCase.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.view.facelets.tag;
+
+import jakarta.el.ExpressionFactory;
+import jakarta.faces.application.ViewHandler;
+import jakarta.faces.component.UIViewRoot;
+
+import org.apache.myfaces.view.facelets.AbstractFaceletTestCase;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * MYFACES-4585: Facelets taglib attributes must be exposed on {@link jakarta.el.VariableMapper}
+ * (as in {@code LegacyUserTagHandler}), not only on {@code TemplateContext}, so EL that is
+ * evaluated outside TemplateContext-only paths resolves correctly.
+ */
+public class UserTagHandlerVariableMapperTestCase extends AbstractFaceletTestCase
+{
+
+    @Override
+    protected void setUpServletObjects() throws Exception
+    {
+        super.setUpServletObjects();
+        servletContext.addInitParameter(ViewHandler.FACELETS_LIBRARIES_PARAM_NAME, "/user4585.taglib.xml");
+    }
+
+    @Override
+    protected ExpressionFactory createExpressionFactory()
+    {
+        return new org.apache.el.ExpressionFactoryImpl();
+    }
+
+    @Test
+    public void testTaglibAttributeAvailableViaVariableMapper4585() throws Exception
+    {
+        facesContext.getExternalContext().getRequestMap().put("user4585Bean", new User4585Bean());
+
+        UIViewRoot root = facesContext.getViewRoot();
+        vdl.buildView(facesContext, root, "user4585_main.xhtml");
+
+        String out = render(root);
+        Assertions.assertTrue(out.contains("OK:z"), "Expected taglib 'converter' to resolve for method call: " + out);
+    }
+
+    /**
+     * Mimics a JSF Converter (or delegate) passed into a taglib as in MYFACES-4585 / PrimeFaces AutoComplete.
+     */
+    public static class User4585Bean
+    {
+        public ConverterHelper getConverter()
+        {
+            return new ConverterHelper();
+        }
+    }
+
+    public static class ConverterHelper
+    {
+        public String getDisplayValue(String s)
+        {
+            return "OK:" + s;
+        }
+    }
+}
diff --git a/impl/src/test/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler4577TestCase.java b/impl/src/test/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler4577TestCase.java
new file mode 100644
index 000000000..1cb1583ad
--- /dev/null
+++ b/impl/src/test/java/org/apache/myfaces/view/facelets/tag/ui/IncludeHandler4577TestCase.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.view.facelets.tag.ui;
+
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIViewRoot;
+import jakarta.faces.component.html.HtmlForm;
+import jakarta.faces.component.html.HtmlOutputText;
+
+import org.apache.myfaces.renderkit.html.HtmlFormRenderer;
+import org.apache.myfaces.renderkit.html.HtmlTextRenderer;
+import org.apache.myfaces.view.facelets.AbstractFaceletTestCase;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * MYFACES-4577: ui:insert nested under ui:include must resolve the enclosing
+ * page's ui:define; components from the included fragment must be in the tree
+ * (e.g. for Ajax update targets).
+ */
+public class IncludeHandler4577TestCase extends AbstractFaceletTestCase
+{
+
+    @Override
+    protected void setupComponents() throws Exception
+    {
+        application.addComponent(UIViewRoot.COMPONENT_TYPE, UIViewRoot.class.getName());
+        application.addComponent(HtmlForm.COMPONENT_TYPE, HtmlForm.class.getName());
+        application.addComponent(HtmlOutputText.COMPONENT_TYPE, HtmlOutputText.class.getName());
+    }
+
+    @Override
+    protected void setupConvertersAndValidators() throws Exception
+    {
+    }
+
+    @Override
+    protected void setupRenderers() throws Exception
+    {
+        renderKit.addRenderer(HtmlForm.COMPONENT_FAMILY, "jakarta.faces.Form", new HtmlFormRenderer());
+        renderKit.addRenderer(HtmlOutputText.COMPONENT_FAMILY, "jakarta.faces.Text", new HtmlTextRenderer());
+    }
+
+    @Test
+    public void testInsertInsideIncludeSeesOuterDefine() throws Exception
+    {
+        UIViewRoot root = facesContext.getViewRoot();
+        vdl.buildView(facesContext, root, "myfaces4577_main.xhtml");
+
+        UIComponent toolbar = root.findComponent("form:toolbar");
+        Assertions.assertNotNull(toolbar, "toolbar from ui:include inside ui:define must be built");
+        Assertions.assertEquals("toolbar", ((HtmlOutputText) toolbar).getValue());
+    }
+}
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/resources/components/cc4589.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/resources/components/cc4589.xhtml
new file mode 100644
index 000000000..a8ad1f26c
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/resources/components/cc4589.xhtml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+-->
+<ui:component xmlns="http://www.w3.org/1999/xhtml"
+              xmlns:cc="http://java.sun.com/jsf/composite"
+              xmlns:h="http://java.sun.com/jsf/html"
+              xmlns:ui="http://java.sun.com/jsf/facelets">
+    <cc:interface/>
+    <cc:implementation>
+        <h:outputText id="out4589" value="#{color.blue}"/>
+    </cc:implementation>
+</ui:component>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_header_fragment.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_header_fragment.xhtml
new file mode 100644
index 000000000..01a55b58a
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_header_fragment.xhtml
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<ui:composition xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:h="http://java.sun.com/jsf/html"
+                xmlns:ui="http://java.sun.com/jsf/facelets">
+
+    <h:outputText id="toolbar" value="toolbar"/>
+
+</ui:composition>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_header_shell.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_header_shell.xhtml
new file mode 100644
index 000000000..54b0ca1d2
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_header_shell.xhtml
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<ui:composition xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:ui="http://java.sun.com/jsf/facelets">
+
+    <div class="layout-header">
+        <ui:insert name="header"/>
+    </div>
+
+</ui:composition>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_main.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_main.xhtml
new file mode 100644
index 000000000..bc30df8bf
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_main.xhtml
@@ -0,0 +1,34 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<!--
+  MYFACES-4577: ui:insert inside ui:include must see the enclosing ui:composition's
+  ui:define (includeDefinition falls back to parent template contexts).
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<ui:composition xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:ui="http://java.sun.com/jsf/facelets"
+                template="myfaces4577_template.xhtml">
+    <ui:define name="header">
+        <ui:include src="myfaces4577_header_fragment.xhtml"/>
+    </ui:define>
+    <ui:define name="content">
+        <span id="contentMarker">content</span>
+    </ui:define>
+</ui:composition>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_template.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_template.xhtml
new file mode 100644
index 000000000..73aa8728a
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/ui/myfaces4577_template.xhtml
@@ -0,0 +1,30 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<ui:composition xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:h="http://java.sun.com/jsf/html"
+                xmlns:ui="http://java.sun.com/jsf/facelets">
+
+    <h:form id="form">
+        <ui:include src="myfaces4577_header_shell.xhtml"/>
+        <ui:insert name="content"/>
+    </h:form>
+
+</ui:composition>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585.taglib.xml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585.taglib.xml
new file mode 100644
index 000000000..83aa3b992
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585.taglib.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<facelet-taglib version="2.0"
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">
+
+    <namespace>http://myfaces.apache.org/test/user4585</namespace>
+
+    <tag>
+        <tag-name>panel</tag-name>
+        <source>user4585_panel.xhtml</source>
+    </tag>
+</facelet-taglib>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585_main.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585_main.xhtml
new file mode 100644
index 000000000..b00495300
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585_main.xhtml
@@ -0,0 +1,31 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<!DOCTYPE html>
+<f:view xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:u="http://myfaces.apache.org/test/user4585">
+<html>
+<h:head/>
+<h:body>
+  <u:panel converter="#{user4585Bean.converter}"/>
+</h:body>
+</html>
+</f:view>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585_panel.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585_panel.xhtml
new file mode 100644
index 000000000..50a9f3770
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4585_panel.xhtml
@@ -0,0 +1,25 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+
+  MYFACES-4585: taglib attribute must be visible on VariableMapper (not only TemplateContext).
+-->
+<ui:composition xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html">
+    <h:outputText id="out4585" value="#{converter.getDisplayValue('z')}"/>
+</ui:composition>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589.taglib.xml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589.taglib.xml
new file mode 100644
index 000000000..8685257d5
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589.taglib.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<facelet-taglib version="2.0"
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">
+
+    <namespace>http://myfaces.apache.org/test/user4589</namespace>
+
+    <tag>
+        <tag-name>panel</tag-name>
+        <source>user4589_panel.xhtml</source>
+    </tag>
+</facelet-taglib>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589_main.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589_main.xhtml
new file mode 100644
index 000000000..cd8df6176
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589_main.xhtml
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!--
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+-->
+<f:view xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:u="http://myfaces.apache.org/test/user4589">
+<html>
+<h:head/>
+<h:body>
+  <u:panel color="red"/>
+</h:body>
+</html>
+</f:view>
diff --git a/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589_panel.xhtml b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589_panel.xhtml
new file mode 100644
index 000000000..46733c321
--- /dev/null
+++ b/impl/src/test/resources/org/apache/myfaces/view/facelets/tag/user4589_panel.xhtml
@@ -0,0 +1,23 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<ui:composition xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:test="http://java.sun.com/jsf/composite/components">
+    <test:cc4589/>
+</ui:composition>