View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.dom;
16  
17  import java.util.ArrayList;
18  import java.util.List;
19  
20  import org.htmlunit.WebClient;
21  import org.htmlunit.corejs.javascript.Callable;
22  import org.htmlunit.corejs.javascript.Context;
23  import org.htmlunit.corejs.javascript.ContextAction;
24  import org.htmlunit.corejs.javascript.Function;
25  import org.htmlunit.corejs.javascript.Scriptable;
26  import org.htmlunit.corejs.javascript.VarScope;
27  import org.htmlunit.html.DomNode;
28  import org.htmlunit.javascript.HtmlUnitContextFactory;
29  import org.htmlunit.javascript.JavaScriptEngine;
30  import org.htmlunit.javascript.configuration.JsxClass;
31  import org.htmlunit.javascript.configuration.JsxConstructor;
32  import org.htmlunit.javascript.configuration.JsxFunction;
33  import org.htmlunit.javascript.configuration.JsxGetter;
34  import org.htmlunit.javascript.configuration.JsxSymbol;
35  
36  /**
37   * An array of elements. Used for the element arrays returned by <code>document.all</code>,
38   * <code>document.all.tags('x')</code>, <code>document.forms</code>, <code>window.frames</code>, etc.
39   * Note that this class must not be used for collections that can be modified, for example
40   * <code>map.areas</code> and <code>select.options</code>.
41   *
42   * @author Daniel Gredler
43   * @author Marc Guillemot
44   * @author Chris Erskine
45   * @author Ahmed Ashour
46   * @author Frank Danek
47   * @author Ronald Brill
48   * @author Lai Quang Duong
49   *
50   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/NodeList">MDN Documentation</a>
51   */
52  @JsxClass
53  public class NodeList extends AbstractList implements Callable {
54  
55      /**
56       * Creates an instance.
57       */
58      public NodeList() {
59          super();
60      }
61  
62      /**
63       * JavaScript constructor.
64       */
65      @JsxConstructor
66      public void jsConstructor() {
67          // nothing to do
68      }
69  
70      /**
71       * Creates an instance.
72       *
73       * @param domNode the {@link DomNode}
74       * @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
75       *        of a descendant node of domNode changes (attribute added, modified or removed)
76       */
77      public NodeList(final DomNode domNode, final boolean attributeChangeSensitive) {
78          super(domNode, attributeChangeSensitive, null);
79      }
80  
81      /**
82       * Constructs an instance with an initial cache value.
83       * @param domNode the parent scope, on which we listen for changes
84       * @param initialElements the initial content for the cache
85       */
86      public NodeList(final DomNode domNode, final List<DomNode> initialElements) {
87          super(domNode, true, new ArrayList<>(initialElements));
88      }
89  
90      /**
91       * Creates an instance.
92       * @param parentScope the parent scope
93       */
94      NodeList(final VarScope parentScope) {
95          super();
96          setParentScope(parentScope);
97          setPrototype(getPrototype(getClass()));
98          setExternalArrayData(parentScope, this);
99      }
100 
101     /**
102      * Gets a static NodeList.
103      *
104      * @param parentScope the parent scope
105      * @param elements the elements
106      * @return an empty collection
107      */
108     public static NodeList staticNodeList(final VarScope parentScope, final List<DomNode> elements) {
109         return new NodeList(parentScope) {
110             @Override
111             public List<DomNode> getElements() {
112                 return elements;
113             }
114         };
115     }
116 
117     /**
118      * Returns an Iterator allowing to go through all keys contained in this object.
119      * @return a NativeArrayIterator
120      */
121     @JsxFunction
122     public Scriptable keys() {
123         return JavaScriptEngine.newArrayIteratorTypeKeys(getParentScope(), this);
124     }
125 
126     /**
127      * Returns an Iterator allowing to go through all keys contained in this object.
128      * @return a NativeArrayIterator
129      */
130     @JsxFunction
131     @JsxSymbol(symbolName = "iterator")
132     public Scriptable values() {
133         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
134     }
135 
136     /**
137      * Returns an Iterator allowing to go through all key/value pairs contained in this object.
138      * @return a NativeArrayIterator
139      */
140     @JsxFunction
141     public Scriptable entries() {
142         return JavaScriptEngine.newArrayIteratorTypeEntries(getParentScope(), this);
143     }
144 
145     /**
146      * Calls the {@code callback} given in parameter once for each value pair in the list, in insertion order.
147      * @param callback function to execute for each element
148      */
149     @JsxFunction
150     public void forEach(final Object callback) {
151         if (!(callback instanceof Function function)) {
152             throw JavaScriptEngine.typeError(
153                     "Foreach callback '" + JavaScriptEngine.toString(callback) + "' is not a function");
154         }
155 
156         if (getElements().isEmpty()) {
157             return;
158         }
159 
160         final WebClient client = getWindow().getWebWindow().getWebClient();
161         final HtmlUnitContextFactory cf = client.getJavaScriptEngine().getContextFactory();
162 
163         final ContextAction<Object> contextAction = cx -> {
164             final VarScope scope = getParentScope();
165 
166             List<DomNode> nodes = getElements();
167             final int size = nodes.size();
168             int i = 0;
169             while (i < size && i < nodes.size()) {
170                 function.call(cx, scope, this, new Object[] {nodes.get(i).getScriptableObject(), i, this});
171 
172                 // refresh
173                 nodes = getElements();
174                 i++;
175             }
176 
177             return null;
178         };
179         cf.call(contextAction);
180     }
181 
182     /**
183      * Returns the length.
184      * @return the length
185      */
186     @JsxGetter
187     @Override
188     public final int getLength() {
189         return super.getLength();
190     }
191 
192     /**
193      * Returns the item or items corresponding to the specified index or key.
194      * @param index the index or key corresponding to the element or elements to return
195      * @return the element or elements corresponding to the specified index or key
196      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/NodeList/item">MDN Documentation</a>
197      */
198     @JsxFunction
199     public Object item(final Object index) {
200         final Object object = getIt(index);
201         if (object == NOT_FOUND) {
202             return null;
203         }
204         return object;
205     }
206 
207     /**
208      * {@inheritDoc}
209      */
210     @Override
211     public Object call(final Context cx, final VarScope scope, final Scriptable thisObj, final Object[] args) {
212         if (args.length == 0) {
213             throw JavaScriptEngine.reportRuntimeError("Zero arguments; need an index or a key.");
214         }
215         final Object object = getIt(args[0]);
216         if (object == NOT_FOUND) {
217             return null;
218         }
219         return object;
220     }
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
226     protected AbstractList create(final DomNode parentScope, final List<DomNode> initialElements) {
227         return new NodeList(parentScope, new ArrayList<>(initialElements));
228     }
229 }