Skip to content

Commit 9bec448

Browse files
committed
NodeList.forEach() now takes care of the current state of the list
1 parent 0608291 commit 9bec448

3 files changed

Lines changed: 145 additions & 5 deletions

File tree

src/changes/changes.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
<body>
1010
<release version="4.12.0" date="April xx, 2025" description="Chrome/Edge 135, Firefox 137, Rhino RegExp, Bugfixes">
11+
<action type="fix" dev="rbri">
12+
NodeList.forEach() now takes care of the current state of the list
13+
(e.g. the forEach function might add/remove elements to the list itself).
14+
</action>
1115
<action type="update" dev="rbri">
1216
AlertHandler, ConfirmHandler, FrameContentHandler, CharacterDataChangeListener, and IncorrectnessListener
1317
marked as @FunctionalInterface.

src/main/java/org/htmlunit/javascript/host/dom/NodeList.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,20 @@ public void forEach(final Object callback) {
152152
"Foreach callback '" + JavaScriptEngine.toString(callback) + "' is not a function");
153153
}
154154

155-
final List<DomNode> nodes = getElements();
156-
157155
final WebClient client = getWindow().getWebWindow().getWebClient();
158156
final HtmlUnitContextFactory cf = client.getJavaScriptEngine().getContextFactory();
159157

160158
final ContextAction<Object> contextAction = cx -> {
161159
final Function function = (Function) callback;
162160
final Scriptable scope = getParentScope();
163-
final int size = nodes.size();
164-
for (int i = 0; i < size; i++) {
165-
function.call(cx, scope, this, new Object[] {nodes.get(i).getScriptableObject(), i, this});
161+
162+
final int size = getElements().size();
163+
int i = 0;
164+
while (i < size) {
165+
function.call(cx, scope, this, new Object[] {getElements().get(i).getScriptableObject(), i, this});
166+
i++;
166167
}
168+
167169
return null;
168170
};
169171
cf.call(contextAction);

src/test/java/org/htmlunit/javascript/host/dom/NodeListTest.java

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,140 @@ public void forEach() throws Exception {
282282
loadPageVerifyTitle2(html);
283283
}
284284

285+
/**
286+
* @throws Exception if an error occurs
287+
*/
288+
@Test
289+
@Alerts({"4", "4",
290+
"[object HTMLElement]/0", "3", "3",
291+
"[object HTMLElement]/1", "2", "2",
292+
"2", "2"})
293+
public void forEachRemove() throws Exception {
294+
final String html = DOCTYPE_HTML
295+
+ "<html><head>\n"
296+
+ "<script>\n"
297+
+ LOG_TITLE_FUNCTION
298+
+ " function test() {\n"
299+
+ " var nodeList = document.getElementById('myId').childNodes;\n"
300+
+ " if (nodeList.forEach) {\n"
301+
+ " log(document.getElementById('myId').childNodes.length);\n"
302+
+ " log(nodeList.length);\n"
303+
304+
+ " nodeList.forEach(myFunction);\n"
305+
306+
+ " log(document.getElementById('myId').childNodes.length);\n"
307+
+ " log(nodeList.length);\n"
308+
+ " } else {\n"
309+
+ " log('no forEach');\n"
310+
+ " }\n"
311+
+ " }\n"
312+
313+
+ " function myFunction(value, index, list, arg) {\n"
314+
+ " log(value + '/' + index);\n"
315+
+ " document.getElementById('myId').removeChild(value);\n"
316+
+ " log(document.getElementById('myId').childNodes.length);\n"
317+
+ " log(list.length);\n"
318+
+ " }\n"
319+
+ "</script>\n"
320+
+ "</head><body onload='test()'>\n"
321+
+ " <div id='myId'><strong>a</strong>b<b>d</b>e</div>\n"
322+
+ "</body></html>";
323+
324+
loadPageVerifyTitle2(html);
325+
}
326+
327+
/**
328+
* @throws Exception if an error occurs
329+
*/
330+
@Test
331+
@Alerts({"4", "4",
332+
"[object HTMLElement]/0", "5", "5",
333+
"[object Text]/1", "6", "6",
334+
"[object HTMLElement]/2", "7", "7",
335+
"[object Text]/3", "8", "8",
336+
"8", "8"})
337+
public void forEachAppend() throws Exception {
338+
final String html = DOCTYPE_HTML
339+
+ "<html><head>\n"
340+
+ "<script>\n"
341+
+ LOG_TITLE_FUNCTION
342+
+ " function test() {\n"
343+
+ " var nodeList = document.getElementById('myId').childNodes;\n"
344+
+ " if (nodeList.forEach) {\n"
345+
+ " log(document.getElementById('myId').childNodes.length);\n"
346+
+ " log(nodeList.length);\n"
347+
348+
+ " nodeList.forEach(myFunction);\n"
349+
350+
+ " log(document.getElementById('myId').childNodes.length);\n"
351+
+ " log(nodeList.length);\n"
352+
+ " } else {\n"
353+
+ " log('no forEach');\n"
354+
+ " }\n"
355+
+ " }\n"
356+
357+
+ " function myFunction(value, index, list, arg) {\n"
358+
+ " log(value + '/' + index);\n"
359+
+ " if (index < 4) {\n"
360+
+ " document.getElementById('myId').appendChild(document.createElement('p'));\n"
361+
+ " }\n"
362+
+ " log(document.getElementById('myId').childNodes.length);\n"
363+
+ " log(list.length);\n"
364+
+ " }\n"
365+
+ "</script>\n"
366+
+ "</head><body onload='test()'>\n"
367+
+ " <div id='myId'><strong>a</strong>b<b>d</b>e</div>\n"
368+
+ "</body></html>";
369+
370+
loadPageVerifyTitle2(html);
371+
}
372+
373+
/**
374+
* @throws Exception if an error occurs
375+
*/
376+
@Test
377+
@Alerts({"4", "4",
378+
"[object HTMLElement]/0", "5", "5",
379+
"[object HTMLElement]/1", "6", "6",
380+
"[object HTMLElement]/2", "7", "7",
381+
"[object HTMLElement]/3", "8", "8",
382+
"8", "8"})
383+
public void forEachInsert() throws Exception {
384+
final String html = DOCTYPE_HTML
385+
+ "<html><head>\n"
386+
+ "<script>\n"
387+
+ LOG_TITLE_FUNCTION
388+
+ " function test() {\n"
389+
+ " var nodeList = document.getElementById('myId').childNodes;\n"
390+
+ " if (nodeList.forEach) {\n"
391+
+ " log(document.getElementById('myId').childNodes.length);\n"
392+
+ " log(nodeList.length);\n"
393+
394+
+ " nodeList.forEach(myFunction);\n"
395+
396+
+ " log(document.getElementById('myId').childNodes.length);\n"
397+
+ " log(nodeList.length);\n"
398+
+ " } else {\n"
399+
+ " log('no forEach');\n"
400+
+ " }\n"
401+
+ " }\n"
402+
403+
+ " function myFunction(value, index, list, arg) {\n"
404+
+ " log(value + '/' + index);\n"
405+
+ " if (index < 4) {\n"
406+
+ " document.getElementById('myId').insertBefore(document.createElement('p'), value);\n"
407+
+ " }\n"
408+
+ " log(document.getElementById('myId').childNodes.length);\n"
409+
+ " log(list.length);\n"
410+
+ " }\n"
411+
+ "</script>\n"
412+
+ "</head><body onload='test()'>\n"
413+
+ " <div id='myId'><strong>a</strong>b<b>d</b>e</div>\n"
414+
+ "</body></html>";
415+
416+
loadPageVerifyTitle2(html);
417+
}
418+
285419
/**
286420
* @throws Exception if the test fails
287421
*/

0 commit comments

Comments
 (0)