Repository: primefaces/primefaces
Pull request #10791 (2023-10-09 19:00:25)

Fix warnings on TreeNode and impls


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1ae7be7b76..32f11e63fd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -40,7 +40,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        java: [11, 21.0.0+35.0.ea-ea]
+        java: [11, 21]
         facesimpl: [ 'mojarra-2.3', 'myfaces-2.3']
     steps:
       - uses: actions/checkout@v4
@@ -63,7 +63,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        java: [11, 21.0.0+35.0.ea-ea]
+        java: [11, 21]
         facesimpl: [ 'mojarra-4.0', 'myfaces-4.0' ]
     steps:
       - uses: actions/checkout@v4
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index c1a0dfd9bd..a81090b930 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -16,7 +16,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        java: [11, 17, 21.0.0+35.0.ea-ea]
+        java: [11, 17, 21]
         profile: [ 'firefox,theme-saga,myfaces-2.3', 'firefox,csp,theme-saga,myfaces-2.3', 'chrome,theme-saga,myfaces-2.3', 'chrome,theme-saga,myfaces-next-2.3','chrome,csp,theme-nova,myfaces-2.3', 'chrome,csp,theme-nova,mojarra-2.3', 'chrome,client-state-saving,theme-nova,mojarra-2.3' ]
 
     steps:
diff --git a/README.md b/README.md
index 77738abe83..6180212f74 100644
--- a/README.md
+++ b/README.md
@@ -73,14 +73,14 @@ For a full list of the available downloads, please visit the [download page](htt
   <dependency>
       <groupId>org.primefaces</groupId>
       <artifactId>primefaces</artifactId>
-      <version>13.0.0</version>
+      <version>13.0.2</version>
   </dependency>
 
   <!-- Jakarta EE / jakarta.* / Faces 3.0 - Faces 4.0  -->	
   <dependency>
       <groupId>org.primefaces</groupId>
       <artifactId>primefaces</artifactId>
-      <version>13.0.0</version>
+      <version>13.0.2</version>
       <classifier>jakarta</classifier>
   </dependency>
   ```
diff --git a/docs/10_0_0/components/wizard.md b/docs/10_0_0/components/wizard.md
index dd50b6d128..6d81385649 100644
--- a/docs/10_0_0/components/wizard.md
+++ b/docs/10_0_0/components/wizard.md
@@ -178,12 +178,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/11_0_0/components/wizard.md b/docs/11_0_0/components/wizard.md
index fe9ee4170d..0075e38591 100644
--- a/docs/11_0_0/components/wizard.md
+++ b/docs/11_0_0/components/wizard.md
@@ -178,12 +178,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/12_0_0/components/wizard.md b/docs/12_0_0/components/wizard.md
index fe9ee4170d..0075e38591 100644
--- a/docs/12_0_0/components/wizard.md
+++ b/docs/12_0_0/components/wizard.md
@@ -178,12 +178,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/13_0_0/components/tabview.md b/docs/13_0_0/components/tabview.md
index 32a8aa28d4..5d9f7a42b5 100644
--- a/docs/13_0_0/components/tabview.md
+++ b/docs/13_0_0/components/tabview.md
@@ -44,7 +44,7 @@ TabView is a container component to group content in tabs.
 | touchable      | null    | Boolean    | Enable touch support (if the browser supports it). Default is the global primefaces.TOUCHABLE, which can be overwritten on component level.
 | multiViewState | false   | Boolean    | Whether to keep TabView state across views, defaults to false.
 | focusOnError   | false   | Boolean    | Whether to focus the first tab that contains an error after form submission.
-| focusOnLastActiveTab   | true   | Boolean    | Whether to focus on the last active tab that a user selected.
+| focusOnLastActiveTab   | false   | Boolean    | Whether to focus on the last active tab that a user selected.
 
 ## Getting started with the TabView
 TabView requires one more child tab components to display. Titles can also be defined by using
diff --git a/docs/13_0_0/components/wizard.md b/docs/13_0_0/components/wizard.md
index fe9ee4170d..0075e38591 100644
--- a/docs/13_0_0/components/wizard.md
+++ b/docs/13_0_0/components/wizard.md
@@ -178,12 +178,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/14_0_0/components/tabview.md b/docs/14_0_0/components/tabview.md
index 32a8aa28d4..5d9f7a42b5 100644
--- a/docs/14_0_0/components/tabview.md
+++ b/docs/14_0_0/components/tabview.md
@@ -44,7 +44,7 @@ TabView is a container component to group content in tabs.
 | touchable      | null    | Boolean    | Enable touch support (if the browser supports it). Default is the global primefaces.TOUCHABLE, which can be overwritten on component level.
 | multiViewState | false   | Boolean    | Whether to keep TabView state across views, defaults to false.
 | focusOnError   | false   | Boolean    | Whether to focus the first tab that contains an error after form submission.
-| focusOnLastActiveTab   | true   | Boolean    | Whether to focus on the last active tab that a user selected.
+| focusOnLastActiveTab   | false   | Boolean    | Whether to focus on the last active tab that a user selected.
 
 ## Getting started with the TabView
 TabView requires one more child tab components to display. Titles can also be defined by using
diff --git a/docs/14_0_0/components/tree.md b/docs/14_0_0/components/tree.md
index c04dce6bea..70ee51ff0e 100644
--- a/docs/14_0_0/components/tree.md
+++ b/docs/14_0_0/components/tree.md
@@ -124,12 +124,79 @@ and tree is created, this mode is suitable for relatively small datasets and pro
 interaction. On the otherhand it’s not suitable for large data since all the data is sent to the client also
 client side tree is stateless.
 
-_Dynamic:_ Dynamic mode uses ajax to fetch the treenodes from server side on demand, compared to
+_Dynamic:_ Dynamic mode uses AJAX to fetch the treenodes from server side on demand, compared to
 the client toggling, dynamic mode has the advantage of dealing with large data because only the
 child nodes of the root node is sent to the client initially and whole tree is lazily populated. When a
 node is expanded, tree only loads the children of the particular expanded node and send to the client
 for display.
 
+## Lazy Loading
+As extension to the dynamic tree, we also offer the `LazyTreeNode` model, which enables lazy loading of child nodes.  
+It takes 2 functions: 
+- one to load the child data
+- one to decide if the current data has children or not (leaf)
+
+```xhtml
+<p:tree value="#{treeBean.root}" var="node">
+    <p:treeNode>
+        <h:outputText value="#{node}"/>
+    </p:treeNode>
+</p:tree>
+```
+```java
+@Named
+@ViewScoped
+public class TreeBean implements Serializable {
+
+    private TreeNode<FileInfo> root;
+
+    @PostConstruct
+    public void init() {
+        FacesContext context = FacesContext.getCurrentInstance();
+        root = new LazyTreeNode<>(new FileInfo(context.getExternalContext().getRealPath("/"), true),
+                (fileInfo) -> listFiles(fileInfo),
+                (fileInfo) -> !fileInfo.isDirectory());
+    }
+
+    public static List<FileInfo> listFiles(FileInfo parentFolder) {
+        List<FileInfo> result = new ArrayList<>();
+
+        File[] files = new File(parentFolder.getPath()).listFiles();
+        if (files != null) {
+            for (File file : files) {
+                result.add(new FileInfo(file.getAbsolutePath(), file.isDirectory()));
+            }
+        }
+
+        return result;
+    }
+
+    public TreeNode getRoot() {
+        return root;
+    }
+
+    @Getter
+    public static class FileInfo implements Serializable {
+
+        private String path;
+        private String name;
+        private boolean directory;
+
+        public FileInfo(String path, boolean directory) {
+            this.path = path;
+            if (this.path.equals(File.separator)) {
+                this.name = this.path;
+            }
+            else {
+                String[] parts = path.split(File.separator.equals("\\") ? "\\\\" : File.separator);
+                this.name = parts[parts.length - 1];
+            }
+            this.directory = directory;
+        }
+    }
+}
+```
+
 ## Multiple TreeNode Types
 It’s a common requirement to display different TreeNode types with a different UI (eg icon).
 Suppose you’re using tree to visualize a company with different departments and different
@@ -234,7 +301,7 @@ public boolean customFilter(TreeNode treeNode, Object filter, Locale locale) {
 ```
 
 ## Ajax Behavior Events
-Tree provides various ajax behavior events.
+Tree provides various AJAX behavior events.
 
 | Event | Listener Parameter | Fired |
 | --- | --- | --- |
diff --git a/docs/14_0_0/components/treetable.md b/docs/14_0_0/components/treetable.md
index 537decc11e..0b281801f6 100644
--- a/docs/14_0_0/components/treetable.md
+++ b/docs/14_0_0/components/treetable.md
@@ -23,7 +23,6 @@ id | null | String | Unique identifier of the component
 allowUnsorting | false | Boolean | Defines whether columns are allowed to be unsorted. Default is false.
 binding | null | Object | An el expression that maps to a server side UIComponent instance in a backing bean
 cellSeparator | null | String | Separator text to use in output mode of editable cells with multiple components.
-cloneOnFilter | false | Boolean | Defines if nodes should be cloned on filter via Cloneable interface or Copy-Constructor (CustomNode(CustomNode original) or CustomNode(String type, Object data, TreeNode parent)). Normally the filtered nodes are new instanceof of DefaultTreeNode.
 currentPageReportTemplate | null | String | Template of the currentPageReport UI.
 dataLocale | null | Object | Locale to be used in features such as sorting, defaults to view locale.
 disabledTextSelection | true | Boolean | Disables text selection on row click.
diff --git a/docs/14_0_0/components/wizard.md b/docs/14_0_0/components/wizard.md
index fe9ee4170d..0075e38591 100644
--- a/docs/14_0_0/components/wizard.md
+++ b/docs/14_0_0/components/wizard.md
@@ -178,12 +178,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/7_0/components/wizard.md b/docs/7_0/components/wizard.md
index 9c4c902a6d..d74797d6c9 100644
--- a/docs/7_0/components/wizard.md
+++ b/docs/7_0/components/wizard.md
@@ -166,12 +166,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/8_0/components/wizard.md b/docs/8_0/components/wizard.md
index b9f838da3f..07537d1d74 100644
--- a/docs/8_0/components/wizard.md
+++ b/docs/8_0/components/wizard.md
@@ -174,12 +174,12 @@ flowListener.
 ```
 ```java
 public String handleFlow(FlowEvent event) {
-    String currentStepId = event.getCurrentStep();
-    String stepToGo = event.getNextStep();
+    String currentStepId = event.getOldStep();
+    String stepToGo = event.getNewStep();
     if(skip)
         return "confirm";
     else
-        return event.getNextStep();
+        return event.getNewStep();
 }
 ```
 Steps here are simply the ids of tab, by using a flowListener you can decide which step to display
diff --git a/docs/migrationguide/14_0_0.md b/docs/migrationguide/14_0_0.md
index 1446d9dda8..94f22873d6 100644
--- a/docs/migrationguide/14_0_0.md
+++ b/docs/migrationguide/14_0_0.md
@@ -33,3 +33,6 @@
 ## File Upload
   * `org.primefaces.model.file.UploadedFile` now required new overridden method `getWebkitRelativePath()` see: https://github.com/primefaces/primefaces/pull/10272
   * Apache Commons FileUpload deprecated
+
+## TreeTable
+  * Removal of `cloneOnFilter` property, when custom node is used, component will systematically clone node by using either 3 args constructor, copy constructor or `Cloneable#clone` method 
diff --git a/pom.xml b/pom.xml
index 95496dc1ea..e60262bc73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -321,7 +321,7 @@
             <dependency>
                 <groupId>org.json</groupId>
                 <artifactId>json</artifactId>
-                <version>20230618</version>
+                <version>20231013</version>
             </dependency>
             <dependency>
                 <groupId>org.owasp.encoder</groupId>
diff --git a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java
index c5fd106ed8..75364f779f 100644
--- a/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java
+++ b/primefaces-selenium/primefaces-selenium-components/src/main/java/org/primefaces/selenium/component/Tree.java
@@ -46,8 +46,9 @@ public abstract class Tree extends AbstractComponent {
     public static final String CHILD_SELECTOR = ".ui-tree-container>.ui-treenode";
 
     public List<TreeNode> getChildren() {
-        return findElements(By.cssSelector(CHILD_SELECTOR)).stream().map(e -> new TreeNode(e, CHILD_SELECTOR, this))
-                    .collect(Collectors.toList());
+        return findElements(By.cssSelector(CHILD_SELECTOR)).stream()
+                .map(e -> new TreeNode(e, CHILD_SELECTOR, this))
+                .collect(Collectors.toList());
     }
 
 }
diff --git a/primefaces-selenium/primefaces-selenium-core/pom.xml b/primefaces-selenium/primefaces-selenium-core/pom.xml
index c4808c18fe..09574ea098 100644
--- a/primefaces-selenium/primefaces-selenium-core/pom.xml
+++ b/primefaces-selenium/primefaces-selenium-core/pom.xml
@@ -56,14 +56,14 @@
         <dependency>
             <groupId>org.seleniumhq.selenium</groupId>
             <artifactId>selenium-java</artifactId>
-            <version>4.13.0</version>
+            <version>4.14.1</version>
         </dependency>
 
         <!-- org.seleniumhq.selenium and com.googlecode.owasp-java-html-sanitizer need guava; resolved version-conflicts -->
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
-            <version>32.1.2-jre</version>
+            <version>32.1.3-jre</version>
         </dependency>
 
         <dependency>
diff --git a/primefaces-showcase/pom.xml b/primefaces-showcase/pom.xml
index c176eec533..00d00458ff 100644
--- a/primefaces-showcase/pom.xml
+++ b/primefaces-showcase/pom.xml
@@ -23,7 +23,7 @@
         <myfaces.version>2.3.10</myfaces.version>
         <myfaces-next.version>2.3-next-M8</myfaces-next.version>
         <resteasy.version>5.0.8.Final</resteasy.version>
-        <jetty.version>9.4.52.v20230823</jetty.version>
+        <jetty.version>9.4.53.v20231009</jetty.version>
         <jsf.project.stage>Production</jsf.project.stage>
     </properties>
 
diff --git a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingView.java b/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingView.java
index acd1c4f0ac..0944c26b9e 100644
--- a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingView.java
+++ b/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingView.java
@@ -33,30 +33,32 @@ import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.PostConstruct;
 import javax.faces.context.FacesContext;
+import org.primefaces.model.LazyTreeNode;
 
 @Named("treeLazyLoadingView")
 @ViewScoped
 public class LazyLoadingView implements Serializable {
 
-    private TreeNode root;
+    private TreeNode<FileInfo> root;
 
     @PostConstruct
     public void init() {
         FacesContext context = FacesContext.getCurrentInstance();
-        FileInfo path = new FileInfo(context.getExternalContext().getRealPath("/"), true);
-        root = new LazyLoadingTreeNode(path, (folder) -> listFiles(folder));
+        root = new LazyTreeNode<>(new FileInfo(context.getExternalContext().getRealPath("/"), true),
+                (fileInfo) -> listFiles(fileInfo),
+                (fileInfo) -> !fileInfo.isDirectory());
     }
 
-    public static List<FileInfo> listFiles(String parentFolder) {
-        File[] files = new File(parentFolder).listFiles();
-        if (files == null) {
-            return new ArrayList<>();
-        }
-
+    public static List<FileInfo> listFiles(FileInfo parentFolder) {
         List<FileInfo> result = new ArrayList<>();
-        for (File file : files) {
-            result.add(new FileInfo(file.getAbsolutePath(), file.isDirectory()));
+
+        File[] files = new File(parentFolder.getPath()).listFiles();
+        if (files != null) {
+            for (File file : files) {
+                result.add(new FileInfo(file.getAbsolutePath(), file.isDirectory()));
+            }
         }
+
         return result;
     }
 
diff --git a/primefaces-showcase/src/main/webapp/ui/data/datatable/group.xhtml b/primefaces-showcase/src/main/webapp/ui/data/datatable/group.xhtml
index d92d28d7b8..61e51da79c 100644
--- a/primefaces-showcase/src/main/webapp/ui/data/datatable/group.xhtml
+++ b/primefaces-showcase/src/main/webapp/ui/data/datatable/group.xhtml
@@ -109,9 +109,7 @@
                             <p:column colspan="#{dtGroupView.yearCount}" headerText="Goals"/>
                         </p:row>
                         <p:row>
-                            <ui:repeat value="#{dtGroupView.years}" var="year">
-                                <p:column headerText="#{year}"/>
-                            </ui:repeat>
+                            <p:columns var="year" value="#{dtGroupView.years}" sortBy="#{player.getGoals(year)}" filterBy="#{player.getGoals(year)}" headerText="#{year}"/>
                         </p:row>
                     </p:columnGroup>
 
diff --git a/primefaces-showcase/src/main/webapp/ui/data/tree/lazyloading.xhtml b/primefaces-showcase/src/main/webapp/ui/data/tree/lazyloading.xhtml
index 3070a24c89..5e8dbfb327 100644
--- a/primefaces-showcase/src/main/webapp/ui/data/tree/lazyloading.xhtml
+++ b/primefaces-showcase/src/main/webapp/ui/data/tree/lazyloading.xhtml
@@ -29,8 +29,4 @@
         </div>
     </ui:define>
 
-    <ui:define name="more-source-tabs">
-        <p:tab title="/org/primefaces/showcase/view/data/tree/LazyLoadingTreeNode.java"/>
-    </ui:define>
-
 </ui:composition>
diff --git a/primefaces/pom.xml b/primefaces/pom.xml
index 5beeb700f9..d3d94e7bca 100644
--- a/primefaces/pom.xml
+++ b/primefaces/pom.xml
@@ -182,7 +182,7 @@
         <dependency>
             <groupId>org.apache.tomcat</groupId>
             <artifactId>tomcat-jasper-el</artifactId>
-            <version>9.0.80</version>
+            <version>9.0.82</version>
             <scope>test</scope>
         </dependency>
         <!-- Content type validation in FileUploadUtilsTest -->
diff --git a/primefaces/src/conf/owasp-suppressions.xml b/primefaces/src/conf/owasp-suppressions.xml
index 4ffa557adc..9263739509 100644
--- a/primefaces/src/conf/owasp-suppressions.xml
+++ b/primefaces/src/conf/owasp-suppressions.xml
@@ -7,9 +7,9 @@
         <cve>CVE-2020-8908</cve>
     </suppress>
     <suppress>
-       <notes><![CDATA[file name: guava.jar PrimeFaces does not use the temp directory functionality that is vulnerable]]></notes>
-       <packageUrl regex="true">^pkg:maven/com\.google\.guava/guava@.*$</packageUrl>
-       <cve>CVE-2023-2976</cve>
+        <notes><![CDATA[file name: guava.jar PrimeFaces does not use the temp directory functionality that is vulnerable]]></notes>
+        <packageUrl regex="true">^pkg:maven/com\.google\.guava/guava@.*$</packageUrl>
+        <cve>CVE-2023-2976</cve>
     </suppress>
     <suppress>
         <notes>
@@ -28,8 +28,13 @@
         <cve>CVE-2021-41568</cve>
     </suppress>
     <suppress>
-        <notes><![CDATA[ file name: json-20230227.jar JSON has been fixed but is still being reported. ]]></notes>
+        <notes><![CDATA[file name: hazelcast-5.3.0.jar (shaded: org.json:json:20230227)]]></notes>
+        <packageUrl regex="true">^pkg:maven/org\.json/json@.*$</packageUrl>
+        <cpe>cpe:/a:json-java_project:json-java</cpe>
+    </suppress>
+    <suppress>
+        <notes><![CDATA[file name: hazelcast-5.3.0.jar (shaded: org.json:json:20230227)]]></notes>
         <packageUrl regex="true">^pkg:maven/org\.json/json@.*$</packageUrl>
-        <cve>CVE-2022-45688</cve>
+        <vulnerabilityName>CVE-2023-5072</vulnerabilityName>
     </suppress>
 </suppressions>
diff --git a/primefaces/src/main/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriter.java b/primefaces/src/main/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriter.java
index 2796cab36f..e294d0b25b 100644
--- a/primefaces/src/main/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriter.java
+++ b/primefaces/src/main/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriter.java
@@ -31,6 +31,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
 
+import javax.faces.FacesException;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.context.ResponseWriter;
@@ -55,6 +56,8 @@ public class MoveScriptsToBottomResponseWriter extends ResponseWriterWrapper {
     private StringBuilder inline;
     private boolean scriptsRendered;
 
+    private boolean foundHtmlElement;
+    private boolean foundBodyElement;
     private boolean writeFouc;
 
     public MoveScriptsToBottomResponseWriter(ResponseWriter wrapped, MoveScriptsToBottomState state) {
@@ -64,6 +67,8 @@ public class MoveScriptsToBottomResponseWriter extends ResponseWriterWrapper {
         inScript = false;
         scriptsRendered = false;
         writeFouc = false;
+        foundHtmlElement = false;
+        foundBodyElement = false;
 
         includeAttributes = new LinkedHashMap<>(6);
         inline = new StringBuilder(75);
@@ -170,9 +175,24 @@ public class MoveScriptsToBottomResponseWriter extends ResponseWriterWrapper {
 
             getWrapped().startElement(name, component);
 
-            if (BODY_TAG.equalsIgnoreCase(name) && isFirefox()) {
-                writeFouc = true;
+            if (BODY_TAG.equalsIgnoreCase(name)) {
+                if (foundBodyElement) {
+                    throw new FacesException("Duplicate <body> elements were found in the response.");
+                }
+                foundBodyElement = true;
+
+                if (isFirefox()) {
+                    writeFouc = true;
+                }
             }
+
+            if (HTML_TAG.equalsIgnoreCase(name)) {
+                if (foundHtmlElement) {
+                    throw new FacesException("Duplicate <html> elements were found in the response.");
+                }
+                foundHtmlElement = true;
+            }
+
         }
     }
 
diff --git a/primefaces/src/main/java/org/primefaces/component/api/UITable.java b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
index e0c29beb71..e577067f97 100644
--- a/primefaces/src/main/java/org/primefaces/component/api/UITable.java
+++ b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
@@ -208,7 +208,7 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
                 filterValue = FilterMeta.resetToNullIfEmpty(filterValue);
             }
 
-            if (filterValue != null) {
+            if (filterValue != null && filterValue.getClass().isArray()) {
                 ValueExpression columnFilterValueVE = column.getValueExpression(ColumnBase.PropertyKeys.filterValue.toString());
                 if (columnFilterValueVE != null && List.class.isAssignableFrom(columnFilterValueVE.getType(context.getELContext()))) {
                     filterValue = Arrays.asList((Object[]) filterValue);
diff --git a/primefaces/src/main/java/org/primefaces/component/tabview/TabViewBase.java b/primefaces/src/main/java/org/primefaces/component/tabview/TabViewBase.java
index 96c8781a1a..52da4fc7e3 100644
--- a/primefaces/src/main/java/org/primefaces/component/tabview/TabViewBase.java
+++ b/primefaces/src/main/java/org/primefaces/component/tabview/TabViewBase.java
@@ -188,7 +188,7 @@ public abstract class TabViewBase extends UITabPanel implements Widget, RTLAware
     }
 
     public boolean isFocusOnLastActiveTab() {
-        return (Boolean) getStateHelper().eval(PropertyKeys.focusOnLastActiveTab, true);
+        return (Boolean) getStateHelper().eval(PropertyKeys.focusOnLastActiveTab, false);
     }
 
     public void setFocusOnLastActiveTab(boolean focusOnLastActiveTab) {
diff --git a/primefaces/src/main/java/org/primefaces/component/tabview/TabViewRenderer.java b/primefaces/src/main/java/org/primefaces/component/tabview/TabViewRenderer.java
index 0f92a4d9e0..a2d7b57d02 100644
--- a/primefaces/src/main/java/org/primefaces/component/tabview/TabViewRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/tabview/TabViewRenderer.java
@@ -119,7 +119,7 @@ public class TabViewRenderer extends CoreRenderer {
                 .attr("scrollable", tabView.isScrollable())
                 .attr("tabindex", tabView.getTabindex(), null)
                 .attr("focusOnError", tabView.isFocusOnError(), false)
-                .attr("focusOnLastActiveTab", tabView.isFocusOnLastActiveTab(), true)
+                .attr("focusOnLastActiveTab", tabView.isFocusOnLastActiveTab(), false)
                 .attr("touchable", ComponentUtils.isTouchable(context, tabView),  true)
                 .attr("multiViewState", tabView.isMultiViewState(), false);
 
diff --git a/primefaces/src/main/java/org/primefaces/component/treetable/feature/FilterFeature.java b/primefaces/src/main/java/org/primefaces/component/treetable/feature/FilterFeature.java
index 815ad298ff..9022e2996a 100644
--- a/primefaces/src/main/java/org/primefaces/component/treetable/feature/FilterFeature.java
+++ b/primefaces/src/main/java/org/primefaces/component/treetable/feature/FilterFeature.java
@@ -23,18 +23,18 @@
  */
 package org.primefaces.component.treetable.feature;
 
-import javax.el.ELContext;
-import javax.faces.context.FacesContext;
-import javax.faces.event.PhaseId;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
+import javax.el.ELContext;
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
 
 import org.primefaces.PrimeFaces;
 import org.primefaces.component.treetable.TreeTable;
@@ -110,7 +110,7 @@ public class FilterFeature implements TreeTableFeature {
         collectFilteredRowKeys(context, tt, root, root, filterBy, filterLocale, filteredRowKeys);
 
         // recreate tree node
-        TreeNode filteredValue = cloneTreeNode(tt, root, root.getParent());
+        TreeNode filteredValue = cloneTreeNode(root, root.getParent());
         createFilteredValueFromRowKeys(tt, root, filteredValue, filteredRowKeys);
 
         tt.updateFilteredValue(context, filteredValue);
@@ -199,7 +199,7 @@ public class FilterFeature implements TreeTableFeature {
 
             for (String rk : filteredRowKeys) {
                 if (rk.equals(rowKeyOfChildNode) || rk.startsWith(rowKeyOfChildNode + "_") || rowKeyOfChildNode.startsWith(rk + "_")) {
-                    TreeNode newNode = cloneTreeNode(tt, childNode, filteredNode);
+                    TreeNode newNode = cloneTreeNode(childNode, filteredNode);
                     if (rk.startsWith(rowKeyOfChildNode + "_")) {
                         newNode.setExpanded(true);
                     }
@@ -211,7 +211,7 @@ public class FilterFeature implements TreeTableFeature {
         }
     }
 
-    protected TreeNode cloneTreeNode(TreeTable tt, TreeNode<?> node, TreeNode<?> parent) {
+    protected TreeNode cloneTreeNode(TreeNode<?> node, TreeNode<?> parent) {
         TreeNode clone = null;
 
         // equals check instead of instanceof to allow subclassing
@@ -222,61 +222,47 @@ public class FilterFeature implements TreeTableFeature {
         else if (DefaultTreeNode.class.equals(node.getClass())) {
             clone = new DefaultTreeNode(node.getType(), node.getData(), parent);
         }
-
-        if (clone == null && tt.isCloneOnFilter()) {
-            if (node instanceof Cloneable) {
-                try {
-                    Method cloneMethod = node.getClass().getMethod("clone");
-                    if (cloneMethod != null) {
-                        cloneMethod.setAccessible(true);
-                        clone = (TreeNode) cloneMethod.invoke(node);
-                    }
-                }
-                catch (NoSuchMethodException e) {
-                    LOGGER.warning(node.getClass().getName() + " declares Cloneable but no clone() method found!");
+        else if (node instanceof Cloneable) {
+            try {
+                Method cloneMethod = node.getClass().getMethod("clone");
+                cloneMethod.setAccessible(true);
+                clone = (TreeNode) cloneMethod.invoke(node);
+            }
+            catch (ReflectiveOperationException e) {
+                throw new FacesException("Cloning using " + node.getClass().getSimpleName() + "#clone() failed", e);
+            }
+        }
+        else {
+            try {
+                Constructor<? extends TreeNode> ctor = node.getClass().getConstructor(node.getClass());
+                clone = ctor.newInstance(node);
+                if (parent != null) {
+                    parent.getChildren().add(clone);
                 }
-                catch (InvocationTargetException | IllegalAccessException e) {
-                    LOGGER.warning(node.getClass().getName() + "#clone() not accessible!");
+            }
+            catch (ReflectiveOperationException e) {
+                if (!(e instanceof NoSuchMethodException)) {
+                    throw new FacesException("Calling copy constructor of " + node.getClass().getSimpleName() + " failed: " + e.getMessage());
                 }
             }
-            else {
+
+            if (clone == null) {
                 try {
-                    Constructor<? extends TreeNode> ctor = node.getClass().getConstructor(node.getClass());
-                    clone = ctor.newInstance(node);
-                    if (parent != null) {
-                        parent.getChildren().add(clone);
-                    }
-                }
-                catch (NoSuchMethodException e) {
-                    // ignore
-                }
-                catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
-                    LOGGER.warning("Could not clone " + node.getClass().getName()
-                            + " via public " + node.getClass().getSimpleName() + "() constructor!");
+                    Constructor<? extends TreeNode> ctor = node.getClass().getConstructor(String.class, Object.class, TreeNode.class);
+                    clone = ctor.newInstance(node.getType(), node.getData(), parent);
                 }
-
-                if (clone == null) {
-                    try {
-                        Constructor<? extends TreeNode> ctor = node.getClass().getConstructor(String.class, Object.class, TreeNode.class);
-                        clone = ctor.newInstance(node.getType(), node.getData(), parent);
-                    }
-                    catch (NoSuchMethodException e) {
-                        // ignore
-                    }
-                    catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
-                        LOGGER.warning("Could not clone " + node.getClass().getName()
-                                + " via public " + node.getClass().getSimpleName() + "(String type, Object data, TreeNode parent) constructor!");
+                catch (ReflectiveOperationException e) {
+                    if (!(e instanceof NoSuchMethodException)) {
+                        throw new FacesException("Calling constructor " + node.getClass().getSimpleName()
+                                + "(String type, Object data, TreeNode parent) failed", e);
                     }
                 }
             }
-        }
 
-        if (clone == null) {
-            if (node instanceof CheckboxTreeNode) {
-                clone = new CheckboxTreeNode(node.getType(), node.getData(), parent);
-            }
-            else {
-                clone = new DefaultTreeNode(node.getType(), node.getData(), parent);
+            if (clone == null) {
+                throw new FacesException("Custom node requires to implement either Cloneable, "
+                        + "a constructor: " + node.getClass().getSimpleName() + "(String type, Object data, TreeNode parent), "
+                        + "or a copy contructor");
             }
         }
 
diff --git a/primefaces/src/main/java/org/primefaces/model/CheckboxTreeNode.java b/primefaces/src/main/java/org/primefaces/model/CheckboxTreeNode.java
index 8913f8bfdd..bf429cd389 100644
--- a/primefaces/src/main/java/org/primefaces/model/CheckboxTreeNode.java
+++ b/primefaces/src/main/java/org/primefaces/model/CheckboxTreeNode.java
@@ -51,23 +51,15 @@ public class CheckboxTreeNode<T> implements TreeNode<T>, Serializable {
     private String rowKey;
 
     public CheckboxTreeNode() {
-        this.type = DEFAULT_TYPE;
-        this.children = new CheckboxTreeNodeChildren(this);
+        this(null);
     }
 
     public CheckboxTreeNode(T data) {
-        this.type = DEFAULT_TYPE;
-        this.children = new CheckboxTreeNodeChildren(this);
-        this.data = data;
+        this(data, null);
     }
 
     public CheckboxTreeNode(T data, TreeNode parent) {
-        this.type = DEFAULT_TYPE;
-        this.data = data;
-        this.children = new CheckboxTreeNodeChildren(this);
-        if (parent != null) {
-            parent.getChildren().add(this);
-        }
+        this(DEFAULT_TYPE, data, parent);
     }
 
     public CheckboxTreeNode(String type, T data, TreeNode parent) {
@@ -144,88 +136,88 @@ public class CheckboxTreeNode<T> implements TreeNode<T>, Serializable {
         return this.selected;
     }
 
-    public void setSelected(boolean value, boolean propagateDown, boolean propagateUp) {
-        this.partialSelected = false;
-        this.selected = value;
+    public void setSelected(boolean selected, boolean propagateDown, boolean propagateUp) {
+        this.selected = selected;
+        partialSelected = false;
 
         if (propagateDown && propagateUp) {
-            this.setSelected(value);
+            setSelected(selected);
         }
         else if (!propagateDown && propagateUp) {
-            if (this.getParent() != null) {
-                ((CheckboxTreeNode) this.getParent()).propagateSelectionUp();
+            if (getParent() != null) {
+                ((CheckboxTreeNode) getParent()).propagateSelectionUp();
             }
         }
-        else if (propagateDown && !propagateUp) {
+        else if (propagateDown) {
             if (!isLeaf()) {
                 for (TreeNode child : children) {
-                    ((CheckboxTreeNode) child).propagateSelectionDown(value);
+                    ((CheckboxTreeNode) child).propagateSelectionDown(selected);
                 }
             }
         }
     }
 
-    public void setSelected(boolean value, boolean propagate) {
+    public void setSelected(boolean selected, boolean propagate) {
         if (propagate) {
-            this.setSelected(value);
+            setSelected(selected);
         }
         else {
-            this.selected = value;
+            this.selected = selected;
         }
     }
 
     @Override
-    public void setSelected(boolean value) {
-        this.selected = value;
+    public void setSelected(boolean selected) {
+        this.selected = selected;
         this.partialSelected = false;
 
         if (!isLeaf()) {
             for (TreeNode child : children) {
-                ((CheckboxTreeNode) child).propagateSelectionDown(value);
+                ((CheckboxTreeNode) child).propagateSelectionDown(selected);
             }
         }
 
-        if (this.getParent() != null) {
-            ((CheckboxTreeNode) this.getParent()).propagateSelectionUp();
+        if (getParent() != null) {
+            ((CheckboxTreeNode) getParent()).propagateSelectionUp();
         }
     }
 
-    protected void propagateSelectionDown(boolean value) {
+    protected void propagateSelectionDown(boolean selected) {
         if (!this.isSelectable()) {
             return;
         }
-        this.selected = value;
+        this.selected = selected;
         this.partialSelected = false;
 
         for (TreeNode child : children) {
-            ((CheckboxTreeNode) child).propagateSelectionDown(value);
+            ((CheckboxTreeNode) child).propagateSelectionDown(selected);
         }
     }
 
     protected void propagateSelectionUp() {
-        if (!this.isSelectable()) {
+        if (!isSelectable()) {
             return;
         }
         boolean allChildrenSelected = true;
-        this.partialSelected = false;
+        partialSelected = false;
 
-        for (int i = 0; i < this.getChildCount(); i++) {
-            TreeNode childNode = this.getChildren().get(i);
+        for (int i = 0; i < getChildCount(); i++) {
+            TreeNode childNode = getChildren().get(i);
 
             boolean childSelected = childNode.isSelected();
             boolean childPartialSelected = childNode.isPartialSelected();
             allChildrenSelected = allChildrenSelected && childSelected;
-            this.partialSelected = this.partialSelected || childSelected || childPartialSelected;
+            partialSelected = partialSelected || childSelected || childPartialSelected;
         }
 
-        this.selected = allChildrenSelected;
+        selected = allChildrenSelected;
 
         if (allChildrenSelected) {
-            this.setPartialSelected(false);
+            setPartialSelected(false);
         }
 
-        if (this.getParent() != null) {
-            ((CheckboxTreeNode) this.getParent()).propagateSelectionUp();
+        if (getParent() != null) {
+            ((CheckboxTreeNode) getParent()).propagateSelectionUp();
         }
     }
 
@@ -298,15 +290,10 @@ public class CheckboxTreeNode<T> implements TreeNode<T>, Serializable {
         }
 
         if (rowKey == null) {
-            if (other.rowKey != null) {
-                return false;
-            }
-        }
-        else if (!rowKey.equals(other.rowKey)) {
-            return false;
+            return other.rowKey == null;
         }
 
-        return true;
+        return rowKey.equals(other.rowKey);
     }
 
     @Override
@@ -325,7 +312,7 @@ public class CheckboxTreeNode<T> implements TreeNode<T>, Serializable {
     }
 
     @Override
-    public void setPartialSelected(boolean value) {
-        this.partialSelected = value;
+    public void setPartialSelected(boolean partialSelected) {
+        this.partialSelected = partialSelected;
     }
 }
diff --git a/primefaces/src/main/java/org/primefaces/model/DefaultTreeNode.java b/primefaces/src/main/java/org/primefaces/model/DefaultTreeNode.java
index a3d133fea9..10b2d55d06 100644
--- a/primefaces/src/main/java/org/primefaces/model/DefaultTreeNode.java
+++ b/primefaces/src/main/java/org/primefaces/model/DefaultTreeNode.java
@@ -50,23 +50,15 @@ public class DefaultTreeNode<T> implements TreeNode<T>, Serializable {
     private String rowKey;
 
     public DefaultTreeNode() {
-        this.type = DEFAULT_TYPE;
-        this.children = initChildren();
+        this(null);
     }
 
     public DefaultTreeNode(T data) {
-        this.type = DEFAULT_TYPE;
-        this.children = initChildren();
-        this.data = data;
+        this(data, null);
     }
 
     public DefaultTreeNode(T data, TreeNode parent) {
-        this.type = DEFAULT_TYPE;
-        this.data = data;
-        this.children = initChildren();
-        if (parent != null) {
-            parent.getChildren().add(this);
-        }
+        this(DEFAULT_TYPE, data, parent);
     }
 
     public DefaultTreeNode(String type, T data, TreeNode parent) {
@@ -220,15 +212,10 @@ public class DefaultTreeNode<T> implements TreeNode<T>, Serializable {
         }
 
         if (rowKey == null) {
-            if (other.rowKey != null) {
-                return false;
-            }
-        }
-        else if (!rowKey.equals(other.rowKey)) {
-            return false;
+            return other.rowKey == null;
         }
 
-        return true;
+        return rowKey.equals(other.rowKey);
     }
 
     @Override
diff --git a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingTreeNode.java b/primefaces/src/main/java/org/primefaces/model/LazyTreeNode.java
similarity index 55%
rename from primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingTreeNode.java
rename to primefaces/src/main/java/org/primefaces/model/LazyTreeNode.java
index 67dacc31cf..e35030918c 100644
--- a/primefaces-showcase/src/main/java/org/primefaces/showcase/view/data/tree/LazyLoadingTreeNode.java
+++ b/primefaces/src/main/java/org/primefaces/model/LazyTreeNode.java
@@ -21,29 +21,34 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-package org.primefaces.showcase.view.data.tree;
+package org.primefaces.model;
 
 import java.util.Collections;
-import org.primefaces.model.DefaultTreeNode;
-import org.primefaces.model.TreeNode;
-
 import java.util.List;
-import java.util.function.Function;
 import java.util.stream.Collectors;
-import org.primefaces.model.TreeNodeChildren;
 
-public class LazyLoadingTreeNode extends DefaultTreeNode<FileInfo> {
+import org.primefaces.util.SerializableFunction;
+
+public class LazyTreeNode<T> extends DefaultTreeNode<T> {
 
-    private Function<String, List<FileInfo>> loadFunction;
-    private boolean lazyLoaded;
+    private SerializableFunction<T, List<T>> loadFunction;
+    private SerializableFunction<T, Boolean> isLeafFunction;
+    private boolean loaded;
+
+    // serialization
+    public LazyTreeNode() {
+    }
 
-    public LazyLoadingTreeNode(FileInfo data, Function<String, List<FileInfo>> loadFunction) {
+    public LazyTreeNode(T data,
+            SerializableFunction<T, List<T>> loadFunction,
+            SerializableFunction<T, Boolean> isLeafFunction) {
         super(data);
         this.loadFunction = loadFunction;
+        this.isLeafFunction = isLeafFunction;
     }
 
     @Override
-    public List<TreeNode<FileInfo>> getChildren() {
+    public List<TreeNode<T>> getChildren() {
         if (isLeaf()) {
             return Collections.emptyList();
         }
@@ -66,49 +71,58 @@ public class LazyLoadingTreeNode extends DefaultTreeNode<FileInfo> {
 
     @Override
     public boolean isLeaf() {
-        return !getData().isDirectory();
+        return isLeafFunction.apply(getData());
     }
 
-    private void lazyLoad() {
-        if (!lazyLoaded) {
-            lazyLoaded = true;
+    public boolean isLoaded() {
+        return loaded;
+    }
 
-            String parentId = ((FileInfo) getData()).getPath();
+    protected void lazyLoad() {
+        if (!loaded) {
+            loaded = true;
 
-            List<LazyLoadingTreeNode> childNodes = loadFunction.apply(parentId).stream()
-                    .map(f -> new LazyLoadingTreeNode(f, loadFunction)).collect(Collectors.toList());
+            List<T> childData = loadFunction.apply(getData());
+            List<LazyTreeNode<T>> childNodes = childData
+                    .stream()
+                    .map(f -> new LazyTreeNode<>(f, loadFunction, isLeafFunction))
+                    .collect(Collectors.toList());
             super.getChildren().addAll(childNodes);
         }
     }
 
     @Override
-    protected List<TreeNode<FileInfo>> initChildren() {
-        return new LazyLoadingTreeNodeChildren(this);
+    protected List<TreeNode<T>> initChildren() {
+        return new LazyTreeNodeChildren(this);
     }
 
-    public static class LazyLoadingTreeNodeChildren extends TreeNodeChildren<FileInfo> {
+    public static class LazyTreeNodeChildren<T> extends TreeNodeChildren<T> {
+
+        // serialization
+        public LazyTreeNodeChildren() {
+        }
 
-        public LazyLoadingTreeNodeChildren(LazyLoadingTreeNode parent) {
+        public LazyTreeNodeChildren(LazyTreeNode parent) {
             super(parent);
         }
 
         @Override
-        protected void updateRowKeys(TreeNode<?> node) {
-            if (((LazyLoadingTreeNode) node).lazyLoaded) {
+        protected void updateRowKeys(TreeNode node) {
+            if (((LazyTreeNode) node).loaded) {
                 super.updateRowKeys(node);
             }
         }
 
         @Override
-        protected void updateRowKeys(int index, TreeNode<?> node) {
-            if (((LazyLoadingTreeNode) node).lazyLoaded) {
+        protected void updateRowKeys(int index, TreeNode node) {
+            if (((LazyTreeNode) node).loaded) {
                 super.updateRowKeys(index, node);
             }
         }
 
         @Override
-        protected void updateRowKeys(TreeNode<?> node, TreeNode<?> childNode, int i) {
-            if (((LazyLoadingTreeNode) node).lazyLoaded) {
+        protected void updateRowKeys(TreeNode node, TreeNode childNode, int i) {
+            if (((LazyTreeNode) node).loaded) {
                 super.updateRowKeys(node, childNode, i);
             }
         }
diff --git a/primefaces/src/main/java/org/primefaces/model/TreeNodeChildren.java b/primefaces/src/main/java/org/primefaces/model/TreeNodeChildren.java
index 77ea0b532d..10f547fc3a 100644
--- a/primefaces/src/main/java/org/primefaces/model/TreeNodeChildren.java
+++ b/primefaces/src/main/java/org/primefaces/model/TreeNodeChildren.java
@@ -33,6 +33,10 @@ public class TreeNodeChildren<T> extends TreeNodeList<T> {
 
     private TreeNode<T> parent;
 
+    // serialization
+    public TreeNodeChildren() {
+    }
+
     public TreeNodeChildren(TreeNode<T> parent) {
         this.parent = parent;
     }
diff --git a/primefaces/src/main/java/org/primefaces/util/HtmlSanitizer.java b/primefaces/src/main/java/org/primefaces/util/HtmlSanitizer.java
index ec93a67289..51cf8e0f5a 100644
--- a/primefaces/src/main/java/org/primefaces/util/HtmlSanitizer.java
+++ b/primefaces/src/main/java/org/primefaces/util/HtmlSanitizer.java
@@ -38,6 +38,14 @@ public class HtmlSanitizer {
             .onElements("img")
             .toFactory();
 
+    private static final PolicyFactory HTML_MEDIA_SANITIZER = new HtmlPolicyBuilder()
+            .allowUrlProtocols("data", "http", "https")
+            .allowElements("video", "audio", "source", "iframe", "figure")
+            .allowAttributes("controls", "width", "height", "origin-size", "src", "allowfullscreen", "class", "style", "data-proportion", "data-align",
+                        "data-percentage", " data-size", "data-file-name", "data-file-size", "data-origin", "data-rotate", "data-index")
+            .onElements("video", "audio", "source", "iframe", "figure")
+            .toFactory();
+
     private static final PolicyFactory HTML_LINKS_SANITIZER = Sanitizers.LINKS
             .and(new HtmlPolicyBuilder()
             .allowElements("a")
@@ -59,7 +67,7 @@ public class HtmlSanitizer {
     }
 
     public static String sanitizeHtml(String value,
-            boolean allowBlocks, boolean allowFormatting, boolean allowLinks, boolean allowStyles, boolean allowImages) {
+            boolean allowBlocks, boolean allowFormatting, boolean allowLinks, boolean allowStyles, boolean allowMedia) {
 
         if (LangUtils.isBlank(value)) {
             return value;
@@ -78,8 +86,9 @@ public class HtmlSanitizer {
         if (allowStyles) {
             sanitizer = sanitizer.and(HTML_STYLES_SANITIZER);
         }
-        if (allowImages) {
+        if (allowMedia) {
             sanitizer = sanitizer.and(HTML_IMAGES_SANITIZER);
+            sanitizer = sanitizer.and(HTML_MEDIA_SANITIZER);
         }
 
         return sanitizer.sanitize(value);
diff --git a/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml b/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
index 50c86891f1..94d8d90884 100644
--- a/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
+++ b/primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
@@ -28109,7 +28109,7 @@
         </attribute>
         <attribute>
             <description>
-                <![CDATA[Whether to focus the first tab that contains an error after form submission.]]>
+                <![CDATA[Whether to focus the first tab that contains an error after form submission. Default is false.]]>
             </description>
             <name>focusOnError</name>
             <required>false</required>
@@ -28117,7 +28117,7 @@
         </attribute>
          <attribute>
             <description>
-                <![CDATA[Whether to focus on the last active tab that a user selected.]]>
+                <![CDATA[Whether to focus on the last active tab that a user selected. Default is false.]]>
             </description>
             <name>focusOnLastActiveTab</name>
             <required>false</required>
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js b/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js
index 2233989c1f..d588da0151 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js
@@ -4713,11 +4713,9 @@ PrimeFaces.widget.DataTable = PrimeFaces.widget.DeferredWidget.extend({
             this.uncheckHeaderCheckbox();
             this.disableHeaderCheckbox();
         }
-        else if(!this.cfg.selectionPageOnly) {
-            if(this.selection.includes('@all')) {
-                this.enableHeaderCheckbox();
-                this.checkHeaderCheckbox();
-            }
+        else if(!this.cfg.selectionPageOnly && this.selection.includes('@all')) {
+            this.enableHeaderCheckbox();
+            this.checkHeaderCheckbox();
         }
         else {
             var checkboxes, selectedCheckboxes, enabledCheckboxes, disabledCheckboxes;
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectonemenu.js b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectonemenu.js
index 3a98ca561c..33a27493b0 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectonemenu.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectonemenu.js
@@ -31,6 +31,7 @@
  * @prop {PrimeFaces.UnbindCallback} [hideOverlayHandler] Unbind callback for the hide overlay handler.
  * @prop {JQuery} input The DOM element for the hidden input with the current value.
  * @prop {boolean} isDynamicLoaded Whether the contents of the overlay panel were loaded.
+ * @prop {boolean} isTabbing Whether the current process is handling the tab key.
  * @prop {JQuery} [items] The DOM elements for the available selectable options.
  * @prop {JQuery} [itemsContainer] The DOM element for the container with the available selectable options.
  * @prop {JQuery} itemsWrapper The DOM element for the wrapper with the container with the available selectable options.
@@ -117,9 +118,9 @@ PrimeFaces.widget.SelectOneMenu = PrimeFaces.widget.DeferredWidget.extend({
         this.cfg.appendTo = PrimeFaces.utils.resolveAppendTo(this, this.jq, this.panel);
         this.cfg.renderPanelContentOnClient = this.cfg.renderPanelContentOnClient === true;
         this.isDynamicLoaded = false;
-
+        this.isTabbing = false;
         this.searchValue = '';
-
+        
         //pfs metadata
         this.input.data(PrimeFaces.CLIENT_ID_DATA, this.id);
 
@@ -660,7 +661,7 @@ PrimeFaces.widget.SelectOneMenu = PrimeFaces.widget.DeferredWidget.extend({
                 break;
 
                 case 'Tab':
-                    $this.handleTabKey();
+                    $this.handleTabKey(e);
                 break;
 
                 case 'Escape':
@@ -808,7 +809,7 @@ PrimeFaces.widget.SelectOneMenu = PrimeFaces.widget.DeferredWidget.extend({
                 break;
 
                 case 'Tab':
-                    $this.handleTabKey();
+                    $this.handleTabKey(e);
                 break;
 
                 case 'Escape':
@@ -939,9 +940,11 @@ PrimeFaces.widget.SelectOneMenu = PrimeFaces.widget.DeferredWidget.extend({
     /**
      * Callback for when the tab key was pressed. Selects the next option.
      * @private
+     * @param {JQuery.TriggeredEvent} event The keyboard event for the TAB.
      */
-    handleTabKey: function() {
-        if(this.panel.is(':visible')) {
+    handleTabKey: function(event) {
+        this.isTabbing = this.panel.is(':visible');
+        if(this.isTabbing) {
             this.selectItem(this.getActiveItem());
         }
     },
@@ -1011,7 +1014,10 @@ PrimeFaces.widget.SelectOneMenu = PrimeFaces.widget.DeferredWidget.extend({
                 onExited: function() {
                     $this.panel.css('z-index', '');
                     $this.keyboardTarget.attr('aria-expanded', false);
-                    $this.keyboardTarget.trigger('focus.ui-selectonemenu');
+                    if (!$this.isTabbing) {
+                        $this.keyboardTarget.trigger('focus.ui-selectonemenu');
+                    }
+                    $this.isTabbing = false;
                 }
             });
         }
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/tabview/tabview.js b/primefaces/src/main/resources/META-INF/resources/primefaces/tabview/tabview.js
index 0ff5d7554c..82ab5915ea 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/tabview/tabview.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/tabview/tabview.js
@@ -78,7 +78,7 @@ PrimeFaces.widget.TabView = PrimeFaces.widget.DeferredWidget.extend({
         this.focusedTabHeader = null;
         this.tabindex = this.cfg.tabindex||0;
         this.cfg.focusOnError = this.cfg.focusOnError || false;
-        this.cfg.focusOnLastActiveTab = this.cfg.focusOnLastActiveTab === undefined ? true : this.cfg.focusOnLastActiveTab;
+        this.cfg.focusOnLastActiveTab = this.cfg.focusOnLastActiveTab || false;
 
         if(this.cfg.scrollable) {
             this.navscroller = this.jq.children('.ui-tabs-navscroller');


Test patch:
diff --git a/primefaces-integration-tests-jakarta/pom.xml b/primefaces-integration-tests-jakarta/pom.xml
index 62cbe714ea..c294497b5a 100644
--- a/primefaces-integration-tests-jakarta/pom.xml
+++ b/primefaces-integration-tests-jakarta/pom.xml
@@ -30,7 +30,7 @@
 
         <!-- dependency versions -->
         <lombok.version>1.18.30</lombok.version>
-        <tomcat.version>10.1.13</tomcat.version>
+        <tomcat.version>10.1.14</tomcat.version>
         <jetty.version>11.0.15</jetty.version>
     </properties>
 
diff --git a/primefaces-integration-tests/pom.xml b/primefaces-integration-tests/pom.xml
index 30a0d78174..ccabe02625 100644
--- a/primefaces-integration-tests/pom.xml
+++ b/primefaces-integration-tests/pom.xml
@@ -33,10 +33,10 @@
         <lombok.version>1.18.30</lombok.version>
         <myfaces.version>2.3.10</myfaces.version>
         <myfaces-next.version>2.3-next-M7</myfaces-next.version>
-        <tomcat.version>9.0.80</tomcat.version>
+        <tomcat.version>9.0.82</tomcat.version>
         <resteasy.version>5.0.8.Final</resteasy.version>
         <owb.version>2.0.27</owb.version>
-        <jetty.version>9.4.52.v20230823</jetty.version>
+        <jetty.version>9.4.53.v20231009</jetty.version>
     </properties>
 
     <dependencies>
diff --git a/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree006.java b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree006.java
new file mode 100644
index 0000000000..7c490a4edc
--- /dev/null
+++ b/primefaces-integration-tests/src/main/java/org/primefaces/integrationtests/tree/Tree006.java
@@ -0,0 +1,93 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import java.io.File;
+import javax.faces.view.ViewScoped;
+import org.primefaces.model.TreeNode;
+
+import javax.inject.Named;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.PostConstruct;
+import javax.faces.application.FacesMessage;
+import javax.faces.context.FacesContext;
+import lombok.Getter;
+import org.primefaces.model.LazyTreeNode;
+
+@Named
+@ViewScoped
+public class Tree006 implements Serializable {
+
+    private TreeNode<FileInfo> root;
+
+    @PostConstruct
+    public void init() {
+        FacesContext context = FacesContext.getCurrentInstance();
+        root = new LazyTreeNode<>(new FileInfo(context.getExternalContext().getRealPath("/"), true),
+                (fileInfo) -> listFiles(fileInfo),
+                (fileInfo) -> !fileInfo.isDirectory());
+    }
+
+    public static List<FileInfo> listFiles(FileInfo parentFolder) {
+        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Lazy load", parentFolder.getPath()));
+
+        List<FileInfo> result = new ArrayList<>();
+
+        File[] files = new File(parentFolder.getPath()).listFiles();
+        if (files != null) {
+            for (File file : files) {
+                result.add(new FileInfo(file.getAbsolutePath(), file.isDirectory()));
+            }
+        }
+
+        return result;
+    }
+
+
+    public TreeNode getRoot() {
+        return root;
+    }
+
+    @Getter
+    public static class FileInfo implements Serializable {
+
+        private String path;
+        private String name;
+        private boolean directory;
+
+        public FileInfo(String path, boolean directory) {
+            this.path = path;
+            if (this.path.equals(File.separator)) {
+                this.name = this.path;
+            }
+            else {
+                String[] parts = path.split(File.separator.equals("\\") ? "\\\\" : File.separator);
+                this.name = parts[parts.length - 1];
+            }
+            this.directory = directory;
+        }
+    }
+}
diff --git a/primefaces-integration-tests/src/main/webapp/tree/tree006.xhtml b/primefaces-integration-tests/src/main/webapp/tree/tree006.xhtml
new file mode 100644
index 0000000000..d59a2bb325
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/tree/tree006.xhtml
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
+    xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:p="http://primefaces.org/ui">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+        <h:form id="form">
+            <p:tree id="tree" value="#{tree006.root}" var="node" dynamic="true">
+                <p:treeNode>
+                    <h:outputText value="#{node.name}"/>
+                </p:treeNode>
+            </p:tree>
+
+            <p:messages id="msgs" showDetail="true">
+                <p:autoUpdate/>
+            </p:messages>
+        </h:form>
+    </h:body>
+</f:view>
+
+</html>
\ No newline at end of file
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree006Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree006Test.java
new file mode 100644
index 0000000000..88822e5edf
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/tree/Tree006Test.java
@@ -0,0 +1,93 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.tree;
+
+import java.io.File;
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.support.FindBy;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.component.Messages;
+import org.primefaces.selenium.component.Tree;
+import org.primefaces.selenium.component.model.tree.TreeNode;
+
+public class Tree006Test extends AbstractTreeTest {
+
+    @Test
+    @Order(1)
+    @DisplayName("Tree: Single selection")
+    public void testSingleSelection(Page page) {
+
+        File webappRoot = new File("src/main/webapp");
+
+        // Assert - loading root folder content
+        Assertions.assertEquals(1, page.messages.getAllMessages().size());
+        String lazyLoadedPath = page.messages.getMessage(0).getDetail().replace("\\", "/");
+        Assertions.assertTrue(lazyLoadedPath.endsWith("primefaces-integration-tests/src/main/webapp/") //src
+            || lazyLoadedPath.endsWith("primefaces-integration-tests/")); //target
+
+        // Arrange
+        Tree tree = page.tree;
+        Assertions.assertNotNull(tree);
+
+        List<TreeNode> children = tree.getChildren();
+        Assertions.assertNotNull(children);
+        Assertions.assertEquals(webappRoot.listFiles().length, children.size() - 1); // -1 because of missing META-INF
+
+        TreeNode child = children.stream()
+                .filter(node -> "accordionpanel".equals(node.getLabelText())).findFirst().orElse(null);
+        Assertions.assertNotNull(child);
+        child = children.stream()
+                .filter(node -> "WEB-INF".equals(node.getLabelText())).findFirst().orElse(null);
+        Assertions.assertNotNull(child);
+
+        // Act
+        child = children.stream()
+                .filter(node -> "tree".equals(node.getLabelText())).findFirst().orElse(null);
+        child.toggle();
+
+        // Assert - loading "tree" folder content
+        Assertions.assertEquals(1, page.messages.getAllMessages().size());
+        lazyLoadedPath = page.messages.getMessage(0).getDetail().replace("\\", "/");
+        Assertions.assertTrue(lazyLoadedPath.endsWith("primefaces-integration-tests/src/main/webapp/tree") //src
+            || lazyLoadedPath.endsWith("primefaces-integration-tests/tree"));  //target
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:tree")
+        Tree tree;
+
+        @FindBy(id = "form:msgs")
+        Messages messages;
+
+        @Override
+        public String getLocation() {
+            return "tree/tree006.xhtml";
+        }
+    }
+}
diff --git a/primefaces/src/test/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriterTest.java b/primefaces/src/test/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriterTest.java
index 81a9c76fa7..9bf9730f02 100644
--- a/primefaces/src/test/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriterTest.java
+++ b/primefaces/src/test/java/org/primefaces/application/resource/MoveScriptsToBottomResponseWriterTest.java
@@ -24,6 +24,7 @@
 package org.primefaces.application.resource;
 
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.matches;
@@ -31,6 +32,7 @@ import static org.mockito.Mockito.*;
 
 import java.io.IOException;
 
+import javax.faces.FacesException;
 import javax.faces.context.ResponseWriter;
 
 import org.junit.jupiter.api.Assertions;
@@ -249,4 +251,30 @@ public class MoveScriptsToBottomResponseWriterTest {
         verify(wrappedWriter).write(contains("inline"));
     }
 
+    @Test
+    public void testDuplicateHtmlElements() throws IOException {
+        writer.startElement("HTML", null);
+        verify(wrappedWriter).startElement("HTML", null);
+
+
+        FacesException exception = assertThrows(FacesException.class, () -> {
+            writer.startElement("HtmL", null);
+        });
+        Assertions.assertEquals("Duplicate <html> elements were found in the response.", exception.getMessage());
+    }
+
+    @Test
+    public void testDuplicateBodyElements() throws IOException {
+        writer.startElement("HTML", null);
+        verify(wrappedWriter).startElement("HTML", null);
+        writer.startElement("BODY", null);
+        verify(wrappedWriter).startElement("BODY", null);
+
+
+        FacesException exception = assertThrows(FacesException.class, () -> {
+            writer.startElement("BoDy", null);
+        });
+        Assertions.assertEquals("Duplicate <body> elements were found in the response.", exception.getMessage());
+    }
+
 }
\ No newline at end of file
diff --git a/primefaces/src/test/java/org/primefaces/component/treetable/feature/TreeTableFilterFeatureTest.java b/primefaces/src/test/java/org/primefaces/component/treetable/feature/TreeTableFilterFeatureTest.java
index cde1c7543b..5c66301826 100644
--- a/primefaces/src/test/java/org/primefaces/component/treetable/feature/TreeTableFilterFeatureTest.java
+++ b/primefaces/src/test/java/org/primefaces/component/treetable/feature/TreeTableFilterFeatureTest.java
@@ -39,9 +39,23 @@ public class TreeTableFilterFeatureTest {
         DefaultTreeNode cloneRoot = new DefaultTreeNode();
         CustomNode<String> customNode = new CustomNode<>("Type", "Name", root);
         TreeTable treeTable = new TreeTable();
-        treeTable.setCloneOnFilter(true);
 
-        TreeNode cloneCustomNode = filterFeature.cloneTreeNode(treeTable, customNode, cloneRoot);
+        TreeNode cloneCustomNode = filterFeature.cloneTreeNode(customNode, cloneRoot);
+
+        Assertions.assertEquals(1, cloneRoot.getChildren().size());
+        Assertions.assertEquals(cloneCustomNode, cloneRoot.getChildren().get(0));
+    }
+
+    @Test
+    public void testCloneTreeNodeWithBuiltInNodeConstructor() {
+        FilterFeature filterFeature = new FilterFeature();
+
+        DefaultTreeNode root = new DefaultTreeNode();
+        DefaultTreeNode cloneRoot = new DefaultTreeNode();
+        DefaultTreeNode<String> regularNode = new DefaultTreeNode<>("Type", "Name", root);
+        TreeTable treeTable = new TreeTable();
+
+        TreeNode cloneCustomNode = filterFeature.cloneTreeNode(regularNode, cloneRoot);
 
         Assertions.assertEquals(1, cloneRoot.getChildren().size());
         Assertions.assertEquals(cloneCustomNode, cloneRoot.getChildren().get(0));