View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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.ScriptableObject;
27  import org.htmlunit.html.DomNode;
28  import org.htmlunit.javascript.HtmlUnitContextFactory;
29  import org.htmlunit.javascript.HtmlUnitScriptable;
30  import org.htmlunit.javascript.JavaScriptEngine;
31  import org.htmlunit.javascript.configuration.JsxClass;
32  import org.htmlunit.javascript.configuration.JsxConstructor;
33  import org.htmlunit.javascript.configuration.JsxFunction;
34  import org.htmlunit.javascript.configuration.JsxGetter;
35  import org.htmlunit.javascript.configuration.JsxSymbol;
36  
37  /**
38   * An array of elements. Used for the element arrays returned by <code>document.all</code>,
39   * <code>document.all.tags('x')</code>, <code>document.forms</code>, <code>window.frames</code>, etc.
40   * Note that this class must not be used for collections that can be modified, for example
41   * <code>map.areas</code> and <code>select.options</code>.
42   *
43   * @author Daniel Gredler
44   * @author Marc Guillemot
45   * @author Chris Erskine
46   * @author Ahmed Ashour
47   * @author Frank Danek
48   * @author Ronald Brill
49   * @author Lai Quang Duong
50   */
51  @JsxClass
52  public class NodeList extends AbstractList implements Callable {
53  
54      /**
55       * Creates an instance.
56       */
57      public NodeList() {
58          super();
59      }
60  
61      /**
62       * JavaScript constructor.
63       */
64      @JsxConstructor
65      public void jsConstructor() {
66          // nothing to do
67      }
68  
69      /**
70       * Creates an instance.
71       *
72       * @param domNode the {@link DomNode}
73       * @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
74       *        of a descendant node of parentScope changes (attribute added, modified or removed)
75       */
76      public NodeList(final DomNode domNode, final boolean attributeChangeSensitive) {
77          super(domNode, attributeChangeSensitive, null);
78      }
79  
80      /**
81       * Constructs an instance with an initial cache value.
82       * @param domNode the parent scope, on which we listen for changes
83       * @param initialElements the initial content for the cache
84       */
85      public NodeList(final DomNode domNode, final List<DomNode> initialElements) {
86          super(domNode, true, new ArrayList<>(initialElements));
87      }
88  
89      /**
90       * Creates an instance.
91       * @param parentScope the parent scope
92       */
93      NodeList(final ScriptableObject parentScope) {
94          super();
95          setParentScope(parentScope);
96          setPrototype(getPrototype(getClass()));
97          setExternalArrayData(this);
98      }
99  
100     /**
101      * Gets a static NodeList.
102      *
103      * @param parentScope the parent scope
104      * @param elements the elements
105      * @return an empty collection
106      */
107     public static NodeList staticNodeList(final HtmlUnitScriptable parentScope, final List<DomNode> elements) {
108         return new NodeList(parentScope) {
109             @Override
110             public List<DomNode> getElements() {
111                 return elements;
112             }
113         };
114     }
115 
116     /**
117      * Returns an Iterator allowing to go through all keys contained in this object.
118      * @return a NativeArrayIterator
119      */
120     @JsxFunction
121     public Scriptable keys() {
122         return JavaScriptEngine.newArrayIteratorTypeKeys(getParentScope(), this);
123     }
124 
125     /**
126      * Returns an Iterator allowing to go through all keys contained in this object.
127      * @return a NativeArrayIterator
128      */
129     @JsxFunction
130     @JsxSymbol(symbolName = "iterator")
131     public Scriptable values() {
132         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
133     }
134 
135     /**
136      * Returns an Iterator allowing to go through all key/value pairs contained in this object.
137      * @return a NativeArrayIterator
138      */
139     @JsxFunction
140     public Scriptable entries() {
141         return JavaScriptEngine.newArrayIteratorTypeEntries(getParentScope(), this);
142     }
143 
144     /**
145      * Calls the {@code callback} given in parameter once for each value pair in the list, in insertion order.
146      * @param callback function to execute for each element
147      */
148     @JsxFunction
149     public void forEach(final Object callback) {
150         if (!(callback instanceof Function)) {
151             throw JavaScriptEngine.typeError(
152                     "Foreach callback '" + JavaScriptEngine.toString(callback) + "' is not a function");
153         }
154 
155         final List<DomNode> nodes = getElements();
156 
157         final WebClient client = getWindow().getWebWindow().getWebClient();
158         final HtmlUnitContextFactory cf = client.getJavaScriptEngine().getContextFactory();
159 
160         final ContextAction<Object> contextAction = cx -> {
161             final Function function = (Function) callback;
162             final Scriptable scope = getParentScope();
163             for (int i = 0; i < nodes.size(); i++) {
164                 function.call(cx, scope, this, new Object[] {nodes.get(i).getScriptableObject(), i, this});
165             }
166             return null;
167         };
168         cf.call(contextAction);
169     }
170 
171     /**
172      * Returns the length.
173      * @return the length
174      */
175     @JsxGetter
176     @Override
177     public final int getLength() {
178         return super.getLength();
179     }
180 
181     /**
182      * Returns the item or items corresponding to the specified index or key.
183      * @param index the index or key corresponding to the element or elements to return
184      * @return the element or elements corresponding to the specified index or key
185      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536460.aspx">MSDN doc</a>
186      */
187     @JsxFunction
188     public Object item(final Object index) {
189         final Object object = getIt(index);
190         if (object == NOT_FOUND) {
191             return null;
192         }
193         return object;
194     }
195 
196     /**
197      * {@inheritDoc}
198      */
199     @Override
200     public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
201         if (args.length == 0) {
202             throw JavaScriptEngine.reportRuntimeError("Zero arguments; need an index or a key.");
203         }
204         final Object object = getIt(args[0]);
205         if (object == NOT_FOUND) {
206             return null;
207         }
208         return object;
209     }
210 
211     /**
212      * {@inheritDoc}
213      */
214     @Override
215     protected AbstractList create(final DomNode parentScope, final List<DomNode> initialElements) {
216         return new NodeList(parentScope, new ArrayList<>(initialElements));
217     }
218 }