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  @JsxClass
51  public class NodeList extends AbstractList implements Callable {
52  
53      /**
54       * Creates an instance.
55       */
56      public NodeList() {
57          super();
58      }
59  
60      /**
61       * JavaScript constructor.
62       */
63      @JsxConstructor
64      public void jsConstructor() {
65          // nothing to do
66      }
67  
68      /**
69       * Creates an instance.
70       *
71       * @param domNode the {@link DomNode}
72       * @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
73       *        of a descendant node of parentScope changes (attribute added, modified or removed)
74       */
75      public NodeList(final DomNode domNode, final boolean attributeChangeSensitive) {
76          super(domNode, attributeChangeSensitive, null);
77      }
78  
79      /**
80       * Constructs an instance with an initial cache value.
81       * @param domNode the parent scope, on which we listen for changes
82       * @param initialElements the initial content for the cache
83       */
84      public NodeList(final DomNode domNode, final List<DomNode> initialElements) {
85          super(domNode, true, new ArrayList<>(initialElements));
86      }
87  
88      /**
89       * Creates an instance.
90       * @param parentScope the parent scope
91       */
92      NodeList(final VarScope parentScope) {
93          super();
94          setParentScope(parentScope);
95          setPrototype(getPrototype(getClass()));
96          setExternalArrayData(parentScope, this);
97      }
98  
99      /**
100      * Gets a static NodeList.
101      *
102      * @param parentScope the parent scope
103      * @param elements the elements
104      * @return an empty collection
105      */
106     public static NodeList staticNodeList(final VarScope parentScope, final List<DomNode> elements) {
107         return new NodeList(parentScope) {
108             @Override
109             public List<DomNode> getElements() {
110                 return elements;
111             }
112         };
113     }
114 
115     /**
116      * Returns an Iterator allowing to go through all keys contained in this object.
117      * @return a NativeArrayIterator
118      */
119     @JsxFunction
120     public Scriptable keys() {
121         return JavaScriptEngine.newArrayIteratorTypeKeys(getParentScope(), this);
122     }
123 
124     /**
125      * Returns an Iterator allowing to go through all keys contained in this object.
126      * @return a NativeArrayIterator
127      */
128     @JsxFunction
129     @JsxSymbol(symbolName = "iterator")
130     public Scriptable values() {
131         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
132     }
133 
134     /**
135      * Returns an Iterator allowing to go through all key/value pairs contained in this object.
136      * @return a NativeArrayIterator
137      */
138     @JsxFunction
139     public Scriptable entries() {
140         return JavaScriptEngine.newArrayIteratorTypeEntries(getParentScope(), this);
141     }
142 
143     /**
144      * Calls the {@code callback} given in parameter once for each value pair in the list, in insertion order.
145      * @param callback function to execute for each element
146      */
147     @JsxFunction
148     public void forEach(final Object callback) {
149         if (!(callback instanceof Function function)) {
150             throw JavaScriptEngine.typeError(
151                     "Foreach callback '" + JavaScriptEngine.toString(callback) + "' is not a function");
152         }
153 
154         if (getElements().isEmpty()) {
155             return;
156         }
157 
158         final WebClient client = getWindow().getWebWindow().getWebClient();
159         final HtmlUnitContextFactory cf = client.getJavaScriptEngine().getContextFactory();
160 
161         final ContextAction<Object> contextAction = cx -> {
162             final VarScope scope = getParentScope();
163 
164             List<DomNode> nodes = getElements();
165             final int size = nodes.size();
166             int i = 0;
167             while (i < size && i < nodes.size()) {
168                 function.call(cx, scope, this, new Object[] {nodes.get(i).getScriptableObject(), i, this});
169 
170                 // refresh
171                 nodes = getElements();
172                 i++;
173             }
174 
175             return null;
176         };
177         cf.call(contextAction);
178     }
179 
180     /**
181      * Returns the length.
182      * @return the length
183      */
184     @JsxGetter
185     @Override
186     public final int getLength() {
187         return super.getLength();
188     }
189 
190     /**
191      * Returns the item or items corresponding to the specified index or key.
192      * @param index the index or key corresponding to the element or elements to return
193      * @return the element or elements corresponding to the specified index or key
194      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536460.aspx">MSDN doc</a>
195      */
196     @JsxFunction
197     public Object item(final Object index) {
198         final Object object = getIt(index);
199         if (object == NOT_FOUND) {
200             return null;
201         }
202         return object;
203     }
204 
205     /**
206      * {@inheritDoc}
207      */
208     @Override
209     public Object call(final Context cx, final VarScope scope, final Scriptable thisObj, final Object[] args) {
210         if (args.length == 0) {
211             throw JavaScriptEngine.reportRuntimeError("Zero arguments; need an index or a key.");
212         }
213         final Object object = getIt(args[0]);
214         if (object == NOT_FOUND) {
215             return null;
216         }
217         return object;
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     @Override
224     protected AbstractList create(final DomNode parentScope, final List<DomNode> initialElements) {
225         return new NodeList(parentScope, new ArrayList<>(initialElements));
226     }
227 }