Skip to content

Commit 5f20542

Browse files
committed
HTMLCollection are iterable now - you can use them in for..of loops (issue #576)
1 parent 895bfc6 commit 5f20542

3 files changed

Lines changed: 114 additions & 0 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
INCOMPATIBLE CHANGE: CookieManager.getPort(URL) removed. Looks like
1313
<a href="http://code.google.com/p/googleappengine/issues/detail?id=4784"> is solved.
1414
</action>
15+
<action type="add" dev="rbri" issue="576">
16+
HTMLCollection are iterable now - you can use them in for..of loops.
17+
</action>
1518
<action type="fix" dev="Lai Quang Duong">
1619
Regexp "[^]" and "[^]" break translation of later char rangen in the regex.
1720
</action>

src/main/java/org/htmlunit/javascript/host/html/HTMLCollection.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@
3232
import java.util.function.Supplier;
3333

3434
import org.htmlunit.BrowserVersion;
35+
import org.htmlunit.corejs.javascript.BaseFunction;
3536
import org.htmlunit.corejs.javascript.Callable;
3637
import org.htmlunit.corejs.javascript.Context;
38+
import org.htmlunit.corejs.javascript.NativeArrayIterator;
39+
import org.htmlunit.corejs.javascript.NativeArrayIterator.ARRAY_ITERATOR_TYPE;
3740
import org.htmlunit.corejs.javascript.ScriptRuntime;
3841
import org.htmlunit.corejs.javascript.Scriptable;
42+
import org.htmlunit.corejs.javascript.ScriptableObject;
43+
import org.htmlunit.corejs.javascript.SymbolKey;
3944
import org.htmlunit.corejs.javascript.Undefined;
4045
import org.htmlunit.html.DomElement;
4146
import org.htmlunit.html.DomNode;
@@ -71,8 +76,18 @@ public class HTMLCollection extends AbstractList implements Callable {
7176
*/
7277
@JsxConstructor({CHROME, EDGE, FF, FF_ESR})
7378
public HTMLCollection() {
79+
defineProperty(SymbolKey.ITERATOR, IteratorMethod_, ScriptableObject.DONTENUM);
7480
}
7581

82+
private static org.htmlunit.corejs.javascript.BaseFunction IteratorMethod_ =
83+
new BaseFunction() {
84+
@Override
85+
public Object call(
86+
final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
87+
return new NativeArrayIterator(scope, thisObj, ARRAY_ITERATOR_TYPE.VALUES);
88+
}
89+
};
90+
7691
/**
7792
* Creates an instance.
7893
* @param domNode parent scope

src/test/java/org/htmlunit/javascript/host/html/HTMLCollectionTest.java

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,102 @@ public void has() throws Exception {
359359
loadPageVerifyTitle2(html);
360360
}
361361

362+
/**
363+
* @throws Exception if the test fails
364+
*/
365+
@Test
366+
@Alerts({"myForm", "mySecondForm"})
367+
public void forOf() throws Exception {
368+
final String html = "<html><head>\n"
369+
+ "<script>\n"
370+
+ LOG_TITLE_FUNCTION
371+
+ " function test() {\n"
372+
+ " for (f of document.forms) {\n"
373+
+ " log(f.name);\n"
374+
+ " }\n"
375+
+ " }\n"
376+
+ "</script>\n"
377+
+ "</head>\n"
378+
+ "<body onload='test()'>\n"
379+
+ "<form name='myForm'></form>\n"
380+
+ "<form name='mySecondForm'></form>\n"
381+
+ "</body></html>";
382+
383+
loadPageVerifyTitle2(html);
384+
}
385+
386+
/**
387+
* @throws Exception if the test fails
388+
*/
389+
@Test
390+
@Alerts({"myForm", "mySecondForm", "dynamicForm", "-", "myForm", "mySecondForm", "dynamicForm"})
391+
public void forOfDynamicAtEnd() throws Exception {
392+
final String html = "<html><head>\n"
393+
+ "<script>\n"
394+
+ LOG_TITLE_FUNCTION
395+
+ " function test() {\n"
396+
+ " var i = 0;"
397+
+ " for (f of document.forms) {\n"
398+
+ " i++;\n"
399+
+ " if (i == 1) {\n"
400+
+ " var frm = document.createElement('FORM');\n"
401+
+ " frm.name = 'dynamicForm';\n"
402+
+ " document.body.appendChild(frm);\n"
403+
+ " }\n"
404+
+ " log(f.name);\n"
405+
+ " }\n"
406+
407+
+ " log('-');\n"
408+
+ " for (f of document.forms) {\n"
409+
+ " log(f.name);\n"
410+
+ " }\n"
411+
+ " }\n"
412+
+ "</script>\n"
413+
+ "</head>\n"
414+
+ "<body onload='test()'>\n"
415+
+ "<form name='myForm'></form>\n"
416+
+ "<form name='mySecondForm'></form>\n"
417+
+ "</body></html>";
418+
419+
loadPageVerifyTitle2(html);
420+
}
421+
422+
/**
423+
* @throws Exception if the test fails
424+
*/
425+
@Test
426+
@Alerts({"myForm", "myForm", "mySecondForm", "-", "dynamicForm", "myForm", "mySecondForm"})
427+
public void forOfDynamicAtStart() throws Exception {
428+
final String html = "<html><head>\n"
429+
+ "<script>\n"
430+
+ LOG_TITLE_FUNCTION
431+
+ " function test() {\n"
432+
+ " var i = 0;"
433+
+ " for (f of document.forms) {\n"
434+
+ " i++;\n"
435+
+ " if (i == 1) {\n"
436+
+ " var frm = document.createElement('FORM');\n"
437+
+ " frm.name = 'dynamicForm';\n"
438+
+ " document.body.insertBefore(frm, document.getElementsByName('myForm')[0]);\n"
439+
+ " }\n"
440+
+ " log(f.name);\n"
441+
+ " }\n"
442+
443+
+ " log('-');\n"
444+
+ " for (f of document.forms) {\n"
445+
+ " log(f.name);\n"
446+
+ " }\n"
447+
+ " }\n"
448+
+ "</script>\n"
449+
+ "</head>\n"
450+
+ "<body onload='test()'>\n"
451+
+ "<form name='myForm'></form>\n"
452+
+ "<form name='mySecondForm'></form>\n"
453+
+ "</body></html>";
454+
455+
loadPageVerifyTitle2(html);
456+
}
457+
362458
/**
363459
* @throws Exception if the test fails
364460
*/

0 commit comments

Comments
 (0)