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