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

bitstorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/master by this push:
     new 4b8235f575 WICKET-7187 added type to JavaScript content header items. 
(#1498)
4b8235f575 is described below

commit 4b8235f575e65cdd505d0ccde59860ae3cec353e
Author: Johan Stuyts <[email protected]>
AuthorDate: Fri Jul 10 14:12:26 2026 +0200

    WICKET-7187 added type to JavaScript content header items. (#1498)
    
    If the type is not set on the header item, almost everything works as 
before. The only change to current behavior is that `equals(...)` and 
`hashCode(...)` of `JavaScriptContentHeaderItem` now also use the type.
    
    The type for JavaScript content header items are separate from the type for 
JavaScript reference header items because:
    
    * Reference header items only support a subset of the browser-processed 
types: `text/javascript` and `module`.
    * Using the same type for both types of header items would break backwards 
compatibility.
    * Some browser-processed types (import maps and speculation rules) deserve 
a dedicated API. Setting the type on `JavaScriptHeaderItem` would result in an 
unclear API when subtypes for these browser-processed types are added: these 
types know their type and will ignore the type set on their base class.
---
 .../head/JavaScriptContentHeaderItemTest.java      | 83 ++++++++++++++++++++++
 .../markup/head/JavaScriptDataBlockTypeTest.java   | 57 +++++++++++++++
 .../JavaScriptBrowserProcessedContentType.java     | 42 +++++++++++
 .../markup/head/JavaScriptContentHeaderItem.java   | 37 ++++++++--
 .../wicket/markup/head/JavaScriptContentType.java  | 28 ++++++++
 .../markup/head/JavaScriptDataBlockType.java       | 82 +++++++++++++++++++++
 6 files changed, 323 insertions(+), 6 deletions(-)

diff --git 
a/wicket-core-tests/src/test/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItemTest.java
 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItemTest.java
new file mode 100644
index 0000000000..66b8572e86
--- /dev/null
+++ 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItemTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.wicket.markup.head;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.apache.wicket.mock.MockWebResponse;
+import org.junit.jupiter.api.Test;
+
+class JavaScriptContentHeaderItemTest
+{
+    @Test
+    void outputsTextJavascriptAsTypeIfNoTypeSet()
+    {
+        JavaScriptContentHeaderItem item = new JavaScriptContentHeaderItem("", 
"the id");
+        MockWebResponse response = new MockWebResponse();
+
+        item.render(response);
+
+        assertEquals("""
+                <script type="text/javascript" id="the id">
+                /*<![CDATA[*/
+                
+                /*]]>*/
+                </script>
+                """, response.getTextResponse().toString());
+    }
+
+    @Test
+    void outputsTypeThatIsSet()
+    {
+        JavaScriptContentHeaderItem item = new JavaScriptContentHeaderItem("", 
"the id")
+                .setType(JavaScriptBrowserProcessedContentType.MODULE);
+        MockWebResponse response = new MockWebResponse();
+
+        item.render(response);
+
+        assertEquals("""
+                <script type="module" id="the id">
+                /*<![CDATA[*/
+                
+                /*]]>*/
+                </script>
+                """, response.getTextResponse().toString());
+    }
+
+    @Test
+    void itemsWithSameJavascriptAndDifferentTypesAreInequal()
+    {
+        JavaScriptContentHeaderItem item1 = new 
JavaScriptContentHeaderItem("", "the id")
+                
.setType(JavaScriptBrowserProcessedContentType.TEXT_JAVASCRIPT);
+        JavaScriptContentHeaderItem item2 = new 
JavaScriptContentHeaderItem("", "the id")
+                .setType(JavaScriptBrowserProcessedContentType.MODULE);
+
+        assertNotEquals(item1, item2);
+    }
+
+    @Test
+    void itemsWithSameJavascriptAndDifferentTypesHaveDifferentHashCodes()
+    {
+        JavaScriptContentHeaderItem item1 = new 
JavaScriptContentHeaderItem("", "the id")
+                
.setType(JavaScriptBrowserProcessedContentType.TEXT_JAVASCRIPT);
+        JavaScriptContentHeaderItem item2 = new 
JavaScriptContentHeaderItem("", "the id")
+                .setType(JavaScriptBrowserProcessedContentType.MODULE);
+
+        assertNotEquals(item1.hashCode(), item2.hashCode());
+    }
+}
diff --git 
a/wicket-core-tests/src/test/java/org/apache/wicket/markup/head/JavaScriptDataBlockTypeTest.java
 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/head/JavaScriptDataBlockTypeTest.java
new file mode 100644
index 0000000000..3a938aa066
--- /dev/null
+++ 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/head/JavaScriptDataBlockTypeTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.wicket.markup.head;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class JavaScriptDataBlockTypeTest
+{
+    @Test
+    void cannotBeCreatedWithNullType()
+    {
+        String message = assertThrows(IllegalArgumentException.class,
+                () -> new JavaScriptDataBlockType(null))
+                .getMessage();
+        assertEquals("Argument 'type' may not be null.", message);
+    }
+
+    @Test
+    void cannotBeCreatedWithTypeThatIsNotAMediaType()
+    {
+        String message = assertThrows(IllegalArgumentException.class,
+                () -> new JavaScriptDataBlockType("Not a media type"))
+                .getMessage();
+        assertEquals("'type' must be a media type (that is: start with a type 
followed by a slash).", message);
+    }
+
+    @Test
+    void cannotBeCreatedWithTypeThatIsAJavaScriptMediaType()
+    {
+        String message = assertThrows(IllegalArgumentException.class,
+                () -> new JavaScriptDataBlockType("text/javascript"))
+                .getMessage();
+        assertEquals("'type' may not be a JavaScript media type.", message);
+    }
+
+    @Test
+    void creationWithTypeThatIsNotAJavaScriptMediaTypeSucceeds()
+    {
+        assertDoesNotThrow(() -> new 
JavaScriptDataBlockType("application/json"));
+    }
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptBrowserProcessedContentType.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptBrowserProcessedContentType.java
new file mode 100644
index 0000000000..8beb46c4c8
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptBrowserProcessedContentType.java
@@ -0,0 +1,42 @@
+/*
+ * 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.wicket.markup.head;
+
+/**
+ * A <a 
href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type#value";>browser-
+ * processed</a> type for {@link JavaScriptContentHeaderItem}. That is, the 
content is not a data block.
+ */
+public enum JavaScriptBrowserProcessedContentType implements 
JavaScriptContentType
+{
+       IMPORT_MAP("importmap"),
+       MODULE("module"),
+       SPECULATION_RULES("speculationrules"),
+       TEXT_JAVASCRIPT("text/javascript");
+
+       private final String type;
+
+       JavaScriptBrowserProcessedContentType(String type)
+       {
+        this.type = type;
+    }
+
+       @Override
+    public String getType()
+       {
+               return type;
+       }
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
index 0c8839d6f4..82cfea8236 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
@@ -26,7 +26,7 @@ import org.apache.wicket.util.string.Strings;
 import org.apache.wicket.util.value.AttributeMap;
 
 /**
- * {@link HeaderItem} for internal (embedded in the header) javascript content.
+ * {@link HeaderItem} for internal (embedded in the header) JavaScript content.
  * 
  * @author papegaaij
  */
@@ -34,13 +34,15 @@ public class JavaScriptContentHeaderItem extends 
JavaScriptHeaderItem
 {
        private final CharSequence javaScript;
 
+       private JavaScriptContentType type = 
JavaScriptBrowserProcessedContentType.TEXT_JAVASCRIPT;
+
        /**
         * Creates a new {@code JavaScriptContentHeaderItem}.
         * 
         * @param javaScript
-        *            javascript content to be rendered.
+        *            JavaScript content to be rendered.
         * @param id
-        *            unique id for the javascript element. This can be null, 
however in that case the
+        *            unique id for the JavaScript element. This can be null, 
however in that case the
         *            ajax header contribution can't detect duplicate script 
fragments.
         */
        public JavaScriptContentHeaderItem(CharSequence javaScript, String id)
@@ -50,18 +52,39 @@ public class JavaScriptContentHeaderItem extends 
JavaScriptHeaderItem
        }
 
        /**
-        * @return javascript content to be rendered.
+        * @return JavaScript content to be rendered.
         */
        public CharSequence getJavaScript()
        {
                return javaScript;
        }
 
+       public JavaScriptContentType getType()
+       {
+               return type;
+       }
+
+       /**
+        * Set the <a 
href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type";>type</a>
 of
+        * the script. If no type is set, it defaults to {@link 
JavaScriptReferenceType#TEXT_JAVASCRIPT}.
+        *
+        * @param type the new type.
+        */
+       public JavaScriptContentHeaderItem setType(final JavaScriptContentType 
type)
+       {
+               this.type = type;
+               return this;
+       }
+
        @Override
        public void render(Response response)
        {
                AttributeMap attributes = new AttributeMap();
-               attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, 
"text/javascript");
+               // No attribute or an empty string works the same as 
`text/javascript`,
+               // but use the latter for backward compatibility.
+               JavaScriptContentType actualType = type == null ?
+                               
JavaScriptBrowserProcessedContentType.TEXT_JAVASCRIPT : type;
+               attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, 
actualType.getType());
                attributes.putAttribute(JavaScriptUtils.ATTR_ID, getId());
                attributes.putAttribute(JavaScriptUtils.ATTR_CSP_NONCE, 
getNonce());
                JavaScriptUtils.writeInlineScript(response, getJavaScript(), 
attributes);
@@ -88,7 +111,8 @@ public class JavaScriptContentHeaderItem extends 
JavaScriptHeaderItem
                if (o == null || getClass() != o.getClass()) return false;
                if (!super.equals(o)) return false;
                JavaScriptContentHeaderItem that = 
(JavaScriptContentHeaderItem) o;
-               return Objects.equals(javaScript, that.javaScript);
+               return Objects.equals(javaScript, that.javaScript) &&
+                               Objects.equals(type, that.type);
        }
 
        @Override
@@ -97,6 +121,7 @@ public class JavaScriptContentHeaderItem extends 
JavaScriptHeaderItem
                // Not using `Objects.hash` for performance reasons
                int result = super.hashCode();
                result = 31 * result + ((javaScript != null) ? 
javaScript.hashCode() : 0);
+               result = 31 * result + ((type != null) ? type.hashCode() : 0);
                return result;
        }
 }
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentType.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentType.java
new file mode 100644
index 0000000000..5cab1d558d
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentType.java
@@ -0,0 +1,28 @@
+/*
+ * 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.wicket.markup.head;
+
+import org.apache.wicket.util.io.IClusterable;
+
+/**
+ * To be used to define the "type" attribute of the script tag written
+ * by a {@link JavaScriptContentHeaderItem}.
+ */
+public interface JavaScriptContentType extends IClusterable
+{
+    String getType();
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptDataBlockType.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptDataBlockType.java
new file mode 100644
index 0000000000..4fe5abb005
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptDataBlockType.java
@@ -0,0 +1,82 @@
+/*
+ * 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.wicket.markup.head;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * A <a 
href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type#value";>data
 block</a>
+ * type (that is, any other value than a browser-processed value) for {@link 
JavaScriptContentHeaderItem}.
+ */
+public class JavaScriptDataBlockType implements JavaScriptContentType
+{
+    private static final Pattern TYPE_PREFIX_PATTERN = 
Pattern.compile("^[-!#$%&'*+.0-9A-Z^_`a-z{|}~]+/");
+    private static final Set<String> JAVASCRIPT_MEDIA_TYPES = Set.of(
+            "text/javascript",
+            "application/javascript",
+            "application/ecmascript",
+            "application/x-ecmascript",
+            "application/x-javascript",
+            "text/ecmascript",
+            "text/javascript1.0",
+            "text/javascript1.1",
+            "text/javascript1.2",
+            "text/javascript1.3",
+            "text/javascript1.4",
+            "text/javascript1.5",
+            "text/jscript",
+            "text/livescript",
+            "text/x-ecmascript",
+            "text/x-javascript"
+    );
+
+    private final String type;
+
+    /**
+     * Create a new data block type with the given media type.
+     * <p>
+     * A rudimentary check is done to ensure <code>type</code> is a media 
type. It must start with a
+     * <a href="https://www.rfc-editor.org/info/rfc9110/#media.type";>token 
followed by a slash</a>. No check is done for
+     * what follows the slash.
+     * <p>
+     * The type may also not be one of the JavaScript media types defined in
+     * <a 
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types#textjavascript";><i>text/javacript</i>
+     * on MDN</a>.
+     *
+     * @param type the media type of the data block.
+     */
+    public JavaScriptDataBlockType(String type)
+    {
+        Args.notNull(type, "type");
+        Args.isTrue(TYPE_PREFIX_PATTERN.matcher(type).find(),
+                "'type' must be a media type (that is: start with a type 
followed by a slash).");
+        
Args.isFalse(JAVASCRIPT_MEDIA_TYPES.contains(type.toLowerCase(Locale.ENGLISH)),
+                "'type' may not be a JavaScript media type.");
+
+        this.type = type;
+    }
+
+    @Override
+    public String getType()
+    {
+        return type;
+    }
+}

Reply via email to