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