Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,23 @@
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
*/
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)
Expand All @@ -50,18 +52,39 @@ public JavaScriptContentHeaderItem(CharSequence javaScript, String id)
}

/**
* @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);
Expand All @@ -88,7 +111,8 @@ public boolean equals(Object o)
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
Expand All @@ -97,6 +121,7 @@ public int hashCode()
// 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;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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>
Comment thread
bitstorm marked this conversation as resolved.
* 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;
}
}