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
4 changes: 4 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

<body>
<release version="5.8.0" date="xxxx xx, 2026" description="Chrome/Edge 157, Bugfixes">
<action type="fix" dev="rbri" issue="#1188" due-to="Vladimir Babin">
DOMTokenList.toggle() now honors the optional force argument: a true value only adds the token
and a false value only removes it, matching the DOM standard.
</action>
<action type="add" dev="rbri" issue="#638">
Content-Security-Policy: XMLHttpRequest supports the connect-src directive (with default-src as fallback);
the request url and all redirect targets are checked, blocked requests fail with a network error.
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/org/htmlunit/javascript/host/dom/DOMTokenList.java
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,12 @@ public boolean replace(final String oldToken, final String newToken) {
/**
* Toggles the token, adding or removing it.
* @param token the token to add or remove
* @param force if {@code true}, only adds the token; if {@code false}, only removes it;
* if not specified, the token is toggled
* @return whether the string now contains the token or not
*/
@JsxFunction
public boolean toggle(final String token) {
public boolean toggle(final String token, final Object force) {
if (org.htmlunit.util.StringUtils.isEmptyOrNull(token)) {
throw JavaScriptEngine.asJavaScriptException(
getWindow(),
Expand All @@ -295,15 +297,18 @@ public boolean toggle(final String token) {
}

final List<String> parts = split(getValue());
if (parts.contains(token)) {
final boolean present = parts.contains(token);
final boolean add = JavaScriptEngine.isUndefined(force) ? !present : JavaScriptEngine.toBoolean(force);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks strange to me, my expectation is more like: if the value for force is not provided, then the value is false, but not dependent on the present variable. I'm wrong here?

final boolean add = JavaScriptEngine.isUndefined(force) ? false : JavaScriptEngine.toBoolean(force);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG looks like the spec requires this.... will merge and lets see...


if (add && !present) {
parts.add(token);
updateAttribute(String.join(" ", parts));
}
else if (!add && present) {
parts.remove(token);
updateAttribute(String.join(" ", parts));
return false;
}

parts.add(token);
updateAttribute(String.join(" ", parts));
return true;
return add;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,85 @@ public void toggleNotDefined() throws Exception {
toggle(null, "a");
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts({"a", "1", "true", "2", "a\\sb",
"<div\\sid=\"d1\"\\sclass=\"a\\sb\"></div>",
"class\\schanged\\sold:\\sa"})
public void toggleForceTrueAdds() throws Exception {
toggle("a", "b", "true");
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts({"a\\sb", "2", "true", "2", "a\\sb",
"<div\\sid=\"d1\"\\sclass=\"a\\sb\"></div>"})
public void toggleForceTrueKeeps() throws Exception {
toggle("a b", "b", "true");
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts({"a\\sb", "2", "false", "1", "a",
"<div\\sid=\"d1\"\\sclass=\"a\"></div>",
"class\\schanged\\sold:\\sa\\sb"})
public void toggleForceFalseRemoves() throws Exception {
toggle("a b", "b", "false");
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts({"a", "1", "false", "1", "a",
"<div\\sid=\"d1\"\\sclass=\"a\"></div>"})
public void toggleForceFalseKeeps() throws Exception {
toggle("a", "b", "false");
}

private void toggle(final String in, final String token, final String force) throws Exception {
final String html = DOCTYPE_HTML
+ "<html><head>\n"
+ "<script>\n"
+ LOG_TITLE_FUNCTION_NORMALIZE
+ " function test() {\n"
+ " var elem = document.getElementById('d1');\n"

+ " var config = { attributes: true, attributeOldValue: true };\n"
+ " var observer = new MutationObserver(function(mutations) {\n"
+ " mutations.forEach(function(mutation) {\n"
+ " log(mutation.attributeName + ' changed old: ' + mutation.oldValue);\n"
+ " });\n"
+ " });\n"
+ " observer.observe(elem, config);"

+ " var list = elem.classList;\n"
+ " if (!list) { log('no list'); return; }\n"

+ " log(elem.className);\n"
+ " log(list.length);\n"
+ " try {\n"
+ " var res = list.toggle('" + token + "', " + force + ");\n"
+ " log(res);\n"
+ " } catch(e) { logEx(e);}\n"
+ " log(list.length);\n"
+ " log(elem.className);\n"
+ " log(elem.outerHTML);\n"
+ " }\n"
+ "</script></head>\n"
+ "<body onload='test()'>\n"
+ " <div id='d1' class='" + in + "'></div>\n"
+ "</body></html>";

loadPageVerifyTitle2(html);
}

private void toggle(final String in, final String token) throws Exception {
String html = DOCTYPE_HTML
+ "<html><head>\n"
Expand Down
Loading