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.html;
16  
17  import java.util.ArrayList;
18  import java.util.List;
19  
20  import org.htmlunit.corejs.javascript.Callable;
21  import org.htmlunit.corejs.javascript.Context;
22  import org.htmlunit.corejs.javascript.Scriptable;
23  import org.htmlunit.corejs.javascript.VarScope;
24  import org.htmlunit.html.DomElement;
25  import org.htmlunit.html.DomNode;
26  import org.htmlunit.html.HtmlForm;
27  import org.htmlunit.html.HtmlInput;
28  import org.htmlunit.javascript.JavaScriptEngine;
29  import org.htmlunit.javascript.configuration.JsxClass;
30  import org.htmlunit.javascript.configuration.JsxConstructor;
31  import org.htmlunit.javascript.configuration.JsxFunction;
32  import org.htmlunit.javascript.configuration.JsxGetter;
33  import org.htmlunit.javascript.configuration.JsxSymbol;
34  import org.htmlunit.javascript.host.dom.AbstractList;
35  
36  /**
37   * A special {@link HTMLCollection} for <code>document.all</code>.
38   *
39   * @author Ronald Brill
40   * @author Ahmed Ashour
41   *
42   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLAllCollection">MDN Documentation</a>
43   */
44  @JsxClass
45  public class HTMLAllCollection extends AbstractList implements Callable {
46  
47      /**
48       * Creates an instance.
49       */
50      public HTMLAllCollection() {
51          super();
52      }
53  
54      /**
55       * JavaScript constructor.
56       */
57      @JsxConstructor
58      public void jsConstructor() {
59          // nothing to do
60      }
61  
62      /**
63       * Creates an instance for the given parent scope.
64       * @param parentScope the parent scope
65       */
66      public HTMLAllCollection(final DomNode parentScope) {
67          super(parentScope, false, null);
68      }
69  
70      /**
71       * Returns the item or items corresponding to the specified index or key.
72       * @param index the index or key corresponding to the element or elements to return
73       * @return the element or elements corresponding to the specified index or key
74       * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLAllCollection/item">MDN Documentation</a>
75       */
76      @JsxFunction
77      public Object item(final Object index) {
78          final double numb;
79  
80          if (index instanceof String name) {
81              final Object result = namedItem(name);
82              if (null != result && !JavaScriptEngine.isUndefined(result)) {
83                  return result;
84              }
85              numb = JavaScriptEngine.toNumber(index);
86              if (Double.isNaN(numb)) {
87                  return null;
88              }
89          }
90          else {
91              numb = JavaScriptEngine.toNumber(index);
92          }
93  
94          if (numb < 0) {
95              return null;
96          }
97  
98          if (Double.isInfinite(numb) || numb != Math.floor(numb)) {
99              return null;
100         }
101 
102         final Object object = get((int) numb, this);
103         if (object == NOT_FOUND) {
104             return null;
105         }
106         return object;
107     }
108 
109     /**
110      * Retrieves the item or items corresponding to the specified name (checks ids, and if
111      * that does not work, then names).
112      * @param name the name or id of the element or elements to return
113      * @return the element or elements corresponding to the specified name or id
114      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLAllCollection/namedItem">MDN Documentation</a>
115      */
116     @JsxFunction
117     public final Scriptable namedItem(final String name) {
118         final List<DomNode> elements = getElements();
119 
120         // See if there is an element in the element array with the specified id.
121         final List<DomElement> matching = new ArrayList<>();
122 
123         for (final DomNode next : elements) {
124             if (next instanceof DomElement elem) {
125                 if (name.equals(elem.getAttributeDirect(DomElement.NAME_ATTRIBUTE))
126                         || name.equals(elem.getId())) {
127                     matching.add(elem);
128                 }
129             }
130         }
131 
132         if (matching.size() == 1) {
133             return getScriptableForElement(matching.get(0));
134         }
135         if (matching.isEmpty()) {
136             return null;
137         }
138 
139         // many elements => build a sub collection
140         final DomNode domNode = getDomNodeOrNull();
141         final List<DomNode> nodes = new ArrayList<>(matching);
142         final HTMLCollection collection = new HTMLCollection(domNode, nodes);
143         collection.setAvoidObjectDetection(true);
144         return collection;
145     }
146 
147     /**
148      * {@inheritDoc}
149      */
150     @Override
151     public Object call(final Context cx, final VarScope scope, final Scriptable thisObj, final Object[] args) {
152         boolean nullIfNotFound = false;
153         if (args[0] instanceof Number number) {
154             final double val = number.doubleValue();
155             if (val != (int) val) {
156                 return null;
157             }
158             if (val >= 0) {
159                 nullIfNotFound = true;
160             }
161         }
162         else {
163             final String val = JavaScriptEngine.toString(args[0]);
164             try {
165                 args[0] = Integer.parseInt(val);
166             }
167             catch (final NumberFormatException ignored) {
168                 // ignore
169             }
170         }
171 
172         if (args.length == 0) {
173             throw JavaScriptEngine.reportRuntimeError("Zero arguments; need an index or a key.");
174         }
175         Object value = getIt(args[0]);
176         if (value == NOT_FOUND) {
177             value = null;
178         }
179         if (nullIfNotFound && JavaScriptEngine.isUndefined(value)) {
180             return null;
181         }
182         return value;
183     }
184 
185     /**
186      * {@inheritDoc}
187      */
188     @Override
189     protected Object equivalentValues(final Object value) {
190         if (value == null || JavaScriptEngine.isUndefined(value)) {
191             return Boolean.TRUE;
192         }
193 
194         return super.equivalentValues(value);
195     }
196 
197     /**
198      * Returns the {@code Symbol.iterator} function that allows iterating over this collection.
199      * @return the Iterator symbol
200      */
201     @JsxSymbol
202     public Scriptable iterator() {
203         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
204     }
205 
206     /**
207      * Returns the length.
208      * @return the length
209      */
210     @JsxGetter
211     @Override
212     public final int getLength() {
213         return super.getLength();
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
220     protected Object getWithPreemptionByName(final String name, final List<DomNode> elements) {
221         final List<DomNode> matchingElements = new ArrayList<>();
222         final boolean searchName = isGetWithPreemptionSearchName();
223         for (final DomNode next : elements) {
224             if (next instanceof DomElement element
225                     && (searchName || next instanceof HtmlInput || next instanceof HtmlForm)) {
226                 final String nodeName = element.getAttributeDirect(DomElement.NAME_ATTRIBUTE);
227                 if (name.equals(nodeName)) {
228                     matchingElements.add(next);
229                 }
230             }
231         }
232 
233         if (matchingElements.isEmpty()) {
234             return NOT_FOUND;
235         }
236 
237         if (matchingElements.size() == 1) {
238             return getScriptableForElement(matchingElements.get(0));
239         }
240 
241         // many elements => build a sub collection
242         final DomNode domNode = getDomNodeOrNull();
243         final HTMLCollection collection = new HTMLCollection(domNode, matchingElements);
244         collection.setAvoidObjectDetection(true);
245         return collection;
246     }
247 
248     /**
249      * Returns whether {@link #getWithPreemption(String)} should search by name or not.
250      * @return whether {@link #getWithPreemption(String)} should search by name or not
251      */
252     protected boolean isGetWithPreemptionSearchName() {
253         return true;
254     }
255 
256     /**
257      * {@inheritDoc}
258      */
259     @Override
260     protected HTMLCollection create(final DomNode parentScope, final List<DomNode> initialElements) {
261         return new HTMLCollection(parentScope, initialElements);
262     }
263 }