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.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.html.DomElement;
24  import org.htmlunit.html.DomNode;
25  import org.htmlunit.html.HtmlForm;
26  import org.htmlunit.html.HtmlInput;
27  import org.htmlunit.javascript.JavaScriptEngine;
28  import org.htmlunit.javascript.configuration.JsxClass;
29  import org.htmlunit.javascript.configuration.JsxConstructor;
30  import org.htmlunit.javascript.configuration.JsxFunction;
31  import org.htmlunit.javascript.configuration.JsxGetter;
32  import org.htmlunit.javascript.configuration.JsxSymbol;
33  import org.htmlunit.javascript.host.dom.AbstractList;
34  
35  /**
36   * A special {@link HTMLCollection} for <code>document.all</code>.
37   *
38   * @author Ronald Brill
39   * @author Ahmed Ashour
40   */
41  @JsxClass
42  public class HTMLAllCollection extends AbstractList implements Callable {
43  
44      /**
45       * Creates an instance.
46       */
47      public HTMLAllCollection() {
48          super();
49      }
50  
51      /**
52       * JavaScript constructor.
53       */
54      @JsxConstructor
55      public void jsConstructor() {
56      }
57  
58      /**
59       * Creates an instance.
60       * @param parentScope parent scope
61       */
62      public HTMLAllCollection(final DomNode parentScope) {
63          super(parentScope, false, null);
64      }
65  
66      /**
67       * Returns the item or items corresponding to the specified index or key.
68       * @param index the index or key corresponding to the element or elements to return
69       * @return the element or elements corresponding to the specified index or key
70       * @see <a href="http://msdn.microsoft.com/en-us/library/ms536460.aspx">MSDN doc</a>
71       */
72      @JsxFunction
73      public Object item(final Object index) {
74          final double numb;
75  
76          if (index instanceof String) {
77              final String name = (String) index;
78              final Object result = namedItem(name);
79              if (null != result && !JavaScriptEngine.isUndefined(result)) {
80                  return result;
81              }
82              numb = JavaScriptEngine.toNumber(index);
83              if (Double.isNaN(numb)) {
84                  return null;
85              }
86          }
87          else {
88              numb = JavaScriptEngine.toNumber(index);
89          }
90  
91          if (numb < 0) {
92              return null;
93          }
94  
95          if (Double.isInfinite(numb) || numb != Math.floor(numb)) {
96              return null;
97          }
98  
99          final Object object = get((int) numb, this);
100         if (object == NOT_FOUND) {
101             return null;
102         }
103         return object;
104     }
105 
106     /**
107      * Retrieves the item or items corresponding to the specified name (checks ids, and if
108      * that does not work, then names).
109      * @param name the name or id the element or elements to return
110      * @return the element or elements corresponding to the specified name or id
111      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536634.aspx">MSDN doc</a>
112      */
113     @JsxFunction
114     public final Scriptable namedItem(final String name) {
115         final List<DomNode> elements = getElements();
116 
117         // See if there is an element in the element array with the specified id.
118         final List<DomElement> matching = new ArrayList<>();
119 
120         for (final DomNode next : elements) {
121             if (next instanceof DomElement) {
122                 final DomElement elem = (DomElement) next;
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 Scriptable scope, final Scriptable thisObj, final Object[] args) {
150         boolean nullIfNotFound = false;
151         if (args[0] instanceof Number) {
152             final double val = ((Number) args[0]).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
222                     && (searchName || next instanceof HtmlInput || next instanceof HtmlForm)) {
223                 final String nodeName = ((DomElement) next).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 }