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          // nothing to do
57      }
58  
59      /**
60       * Creates an instance.
61       * @param parentScope parent scope
62       */
63      public HTMLAllCollection(final DomNode parentScope) {
64          super(parentScope, false, null);
65      }
66  
67      /**
68       * Returns the item or items corresponding to the specified index or key.
69       * @param index the index or key corresponding to the element or elements to return
70       * @return the element or elements corresponding to the specified index or key
71       * @see <a href="http://msdn.microsoft.com/en-us/library/ms536460.aspx">MSDN doc</a>
72       */
73      @JsxFunction
74      public Object item(final Object index) {
75          final double numb;
76  
77          if (index instanceof String) {
78              final String name = (String) index;
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) {
123                 final DomElement elem = (DomElement) next;
124                 if (name.equals(elem.getAttributeDirect(DomElement.NAME_ATTRIBUTE))
125                         || name.equals(elem.getId())) {
126                     matching.add(elem);
127                 }
128             }
129         }
130 
131         if (matching.size() == 1) {
132             return getScriptableForElement(matching.get(0));
133         }
134         if (matching.isEmpty()) {
135             return null;
136         }
137 
138         // many elements => build a sub collection
139         final DomNode domNode = getDomNodeOrNull();
140         final List<DomNode> nodes = new ArrayList<>(matching);
141         final HTMLCollection collection = new HTMLCollection(domNode, nodes);
142         collection.setAvoidObjectDetection(true);
143         return collection;
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     @Override
150     public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
151         boolean nullIfNotFound = false;
152         if (args[0] instanceof Number) {
153             final double val = ((Number) args[0]).doubleValue();
154             if (val != (int) val) {
155                 return null;
156             }
157             if (val >= 0) {
158                 nullIfNotFound = true;
159             }
160         }
161         else {
162             final String val = JavaScriptEngine.toString(args[0]);
163             try {
164                 args[0] = Integer.parseInt(val);
165             }
166             catch (final NumberFormatException ignored) {
167                 // ignore
168             }
169         }
170 
171         if (args.length == 0) {
172             throw JavaScriptEngine.reportRuntimeError("Zero arguments; need an index or a key.");
173         }
174         Object value = getIt(args[0]);
175         if (value == NOT_FOUND) {
176             value = null;
177         }
178         if (nullIfNotFound && JavaScriptEngine.isUndefined(value)) {
179             return null;
180         }
181         return value;
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
188     protected Object equivalentValues(final Object value) {
189         if (value == null || JavaScriptEngine.isUndefined(value)) {
190             return Boolean.TRUE;
191         }
192 
193         return super.equivalentValues(value);
194     }
195 
196     /**
197      * @return the Iterator symbol
198      */
199     @JsxSymbol
200     public Scriptable iterator() {
201         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
202     }
203 
204     /**
205      * Returns the length.
206      * @return the length
207      */
208     @JsxGetter
209     @Override
210     public final int getLength() {
211         return super.getLength();
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     @Override
218     protected Object getWithPreemptionByName(final String name, final List<DomNode> elements) {
219         final List<DomNode> matchingElements = new ArrayList<>();
220         final boolean searchName = isGetWithPreemptionSearchName();
221         for (final DomNode next : elements) {
222             if (next instanceof DomElement
223                     && (searchName || next instanceof HtmlInput || next instanceof HtmlForm)) {
224                 final String nodeName = ((DomElement) next).getAttributeDirect(DomElement.NAME_ATTRIBUTE);
225                 if (name.equals(nodeName)) {
226                     matchingElements.add(next);
227                 }
228             }
229         }
230 
231         if (matchingElements.isEmpty()) {
232             return NOT_FOUND;
233         }
234 
235         if (matchingElements.size() == 1) {
236             return getScriptableForElement(matchingElements.get(0));
237         }
238 
239         // many elements => build a sub collection
240         final DomNode domNode = getDomNodeOrNull();
241         final HTMLCollection collection = new HTMLCollection(domNode, matchingElements);
242         collection.setAvoidObjectDetection(true);
243         return collection;
244     }
245 
246     /**
247      * Returns whether {@link #getWithPreemption(String)} should search by name or not.
248      * @return whether {@link #getWithPreemption(String)} should search by name or not
249      */
250     protected boolean isGetWithPreemptionSearchName() {
251         return true;
252     }
253 
254     /**
255      * {@inheritDoc}
256      */
257     @Override
258     protected HTMLCollection create(final DomNode parentScope, final List<DomNode> initialElements) {
259         return new HTMLCollection(parentScope, initialElements);
260     }
261 }