Repository: primefaces/primefaces (java)

Issue:
MOVE_SCRIPTS_TO_BOTTOM breaks with facelets
### Describe the bug

Using a `ui:composition` together with a `ui:decorate` or `ui:include` on the same page causes PrimeFaces to initialize multiple times when used in combination with `primefaces.MOVE_SCRIPTS_TO_BOTTOM`, causing the behavior of the components to break.

Inspecting the raw HTML, it's evident the same block of code is being run twice.

### PrimeFaces edition

Community

### PrimeFaces version

13.0.0

### JSF implementation

Mojarra

### JSF version

2.3.14

### Java version

11

### Browser(s)

Chrome 117

Pull request description:
Fix #10790: MoveScriptsToBottom fail if duplicate html/body
Fix #10790: MoveScriptsToBottom fail if duplicate html/body

@tandraschko I thought this was safe to do here and I unit tested it and verified it with the user's reproducer.  I wanted to do this here because I think this is the 3rd or 4th time here or on Stack Overflow someone has reported this and I have debugged it.

Patch:
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;
+            }
+
         }
     }
 


Test patch:
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