This is an automated email from the ASF dual-hosted git repository.
bitstorm pushed a commit to branch wicket-10.x
in repository https://gitbox.apache.org/repos/asf/wicket.git
The following commit(s) were added to refs/heads/wicket-10.x by this push:
new 7f2bb48868 WICKET-7187 added type to JavaScript content header items.
(#1511)
7f2bb48868 is described below
commit 7f2bb48868208ddff387c7dd53692b8de5ce777a
Author: Johan Stuyts <[email protected]>
AuthorDate: Fri Jul 10 12:13:27 2026 +0200
WICKET-7187 added type to JavaScript content header items. (#1511)
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;
+ }
+}