View Javadoc
1   /*
2    * Copyright (c) 2002-2018 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    * http://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 com.gargoylesoftware.htmlunit.javascript.host.html;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLALLCOLLECTION_DO_NOT_CONVERT_STRINGS_TO_NUMBER;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLALLCOLLECTION_DO_NOT_SUPPORT_PARANTHESES;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLALLCOLLECTION_INTEGER_INDEX;
20  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLALLCOLLECTION_NO_COLLECTION_FOR_MANY_HITS;
21  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLALLCOLLECTION_NULL_IF_NAMED_ITEM_NOT_FOUND;
22  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLCOLLECTION_ITEM_FUNCT_SUPPORTS_DOUBLE_INDEX_ALSO;
23  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
24  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
25  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
26  
27  import java.util.ArrayList;
28  import java.util.List;
29  
30  import com.gargoylesoftware.htmlunit.BrowserVersion;
31  import com.gargoylesoftware.htmlunit.html.DomElement;
32  import com.gargoylesoftware.htmlunit.html.DomNode;
33  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
34  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
35  
36  import net.sourceforge.htmlunit.corejs.javascript.Context;
37  import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
38  import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
39  import net.sourceforge.htmlunit.corejs.javascript.Undefined;
40  
41  /**
42   * A special {@link HTMLCollection} for <code>document.all</code>.
43   *
44   * @author Ronald Brill
45   * @author Ahmed Ashour
46   */
47  @JsxClass
48  public class HTMLAllCollection extends HTMLCollection {
49  
50      /**
51       * Creates an instance.
52       */
53      @JsxConstructor({CHROME, FF, EDGE})
54      public HTMLAllCollection() {
55      }
56  
57      /**
58       * Creates an instance.
59       * @param parentScope parent scope
60       */
61      public HTMLAllCollection(final DomNode parentScope) {
62          super(parentScope, false);
63      }
64  
65      /**
66       * Returns the item or items corresponding to the specified index or key.
67       * @param index the index or key corresponding to the element or elements to return
68       * @return the element or elements corresponding to the specified index or key
69       * @see <a href="http://msdn.microsoft.com/en-us/library/ms536460.aspx">MSDN doc</a>
70       */
71      @Override
72      public Object item(final Object index) {
73          Double numb;
74  
75          final BrowserVersion browser;
76          if (index instanceof String) {
77              final String name = (String) index;
78              final Object result = namedItem(name);
79              if (null != result && Undefined.instance != result) {
80                  return result;
81              }
82              numb = Double.NaN;
83  
84              browser = getBrowserVersion();
85              if (!browser.hasFeature(HTMLALLCOLLECTION_DO_NOT_CONVERT_STRINGS_TO_NUMBER)) {
86                  numb = ScriptRuntime.toNumber(index);
87              }
88              if (numb.isNaN()) {
89                  return null;
90              }
91          }
92          else {
93              numb = ScriptRuntime.toNumber(index);
94              browser = getBrowserVersion();
95          }
96  
97          if (numb < 0) {
98              return null;
99          }
100 
101         if (!browser.hasFeature(HTMLCOLLECTION_ITEM_FUNCT_SUPPORTS_DOUBLE_INDEX_ALSO)
102                 && (Double.isInfinite(numb) || numb != Math.floor(numb))) {
103             return null;
104         }
105 
106         final Object object = get(numb.intValue(), this);
107         if (object == NOT_FOUND) {
108             return null;
109         }
110         return object;
111     }
112 
113     /**
114      * {@inheritDoc}
115      */
116     @Override
117     public final Object 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         final BrowserVersion browser = getBrowserVersion();
124         for (final DomNode next : elements) {
125             if (next instanceof DomElement) {
126                 final DomElement elem = (DomElement) next;
127                 if (name.equals(elem.getAttributeDirect("name"))
128                         || name.equals(elem.getId())) {
129                     matching.add(elem);
130                 }
131             }
132         }
133 
134         if (matching.size() == 1
135                 || (matching.size() > 1
136                         && browser.hasFeature(HTMLALLCOLLECTION_NO_COLLECTION_FOR_MANY_HITS))) {
137             return getScriptableForElement(matching.get(0));
138         }
139         if (matching.isEmpty()) {
140             if (browser.hasFeature(HTMLALLCOLLECTION_NULL_IF_NAMED_ITEM_NOT_FOUND)) {
141                 return null;
142             }
143             return Undefined.instance;
144         }
145 
146         // many elements => build a sub collection
147         final DomNode domNode = getDomNodeOrNull();
148         final List<DomNode> nodes = new ArrayList<>(matching);
149         final HTMLCollection collection = new HTMLCollection(domNode, nodes);
150         collection.setAvoidObjectDetection(true);
151         return collection;
152     }
153 
154     /**
155      * {@inheritDoc}
156      */
157     @Override
158     public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
159         final BrowserVersion browser = getBrowserVersion();
160         if (browser.hasFeature(HTMLALLCOLLECTION_DO_NOT_SUPPORT_PARANTHESES)) {
161             if (args.length == 0) {
162                 throw Context.reportRuntimeError("Zero arguments; need an index or a key.");
163             }
164 
165             if (args[0] instanceof Number) {
166                 return null;
167             }
168         }
169 
170         boolean nullIfNotFound = false;
171         if (browser.hasFeature(HTMLALLCOLLECTION_INTEGER_INDEX)) {
172             if (args[0] instanceof Number) {
173                 final double val = ((Number) args[0]).doubleValue();
174                 if (val != (int) val) {
175                     return null;
176                 }
177                 if (val >= 0) {
178                     nullIfNotFound = true;
179                 }
180             }
181             else {
182                 final String val = Context.toString(args[0]);
183                 try {
184                     args[0] = Integer.parseInt(val);
185                 }
186                 catch (final NumberFormatException e) {
187                     // ignore
188                 }
189             }
190         }
191 
192         final Object value = super.call(cx, scope, thisObj, args);
193         if (nullIfNotFound && value == Undefined.instance) {
194             return null;
195         }
196         return value;
197     }
198 
199     /**
200      * {@inheritDoc}
201      */
202     @Override
203     protected boolean supportsParentheses() {
204         return true;
205     }
206 }