PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/htmlunit-2.8/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLCollection.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 708 lines | 433 code | 65 blank | 210 comment | 115 complexity | 32d46c93b9ad66dcd80e95f15d41ca4c MD5 | raw file
  1. /*
  2. * Copyright (c) 2002-2010 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. import java.util.ArrayList;
  17. import java.util.List;
  18. import net.sourceforge.htmlunit.corejs.javascript.Context;
  19. import net.sourceforge.htmlunit.corejs.javascript.Function;
  20. import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
  21. import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
  22. import org.apache.commons.collections.CollectionUtils;
  23. import org.apache.commons.collections.Transformer;
  24. import org.apache.commons.collections.functors.NOPTransformer;
  25. import org.apache.commons.logging.Log;
  26. import org.apache.commons.logging.LogFactory;
  27. import org.w3c.dom.Node;
  28. import org.w3c.dom.NodeList;
  29. import com.gargoylesoftware.htmlunit.BrowserVersion;
  30. import com.gargoylesoftware.htmlunit.BrowserVersionFeatures;
  31. import com.gargoylesoftware.htmlunit.WebWindow;
  32. import com.gargoylesoftware.htmlunit.html.DomChangeEvent;
  33. import com.gargoylesoftware.htmlunit.html.DomChangeListener;
  34. import com.gargoylesoftware.htmlunit.html.DomElement;
  35. import com.gargoylesoftware.htmlunit.html.DomNode;
  36. import com.gargoylesoftware.htmlunit.html.DomText;
  37. import com.gargoylesoftware.htmlunit.html.FrameWindow;
  38. import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeEvent;
  39. import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeListener;
  40. import com.gargoylesoftware.htmlunit.html.HtmlElement;
  41. import com.gargoylesoftware.htmlunit.html.HtmlNoScript;
  42. import com.gargoylesoftware.htmlunit.html.xpath.XPathUtils;
  43. import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
  44. import com.gargoylesoftware.htmlunit.javascript.configuration.JavaScriptConfiguration;
  45. import com.gargoylesoftware.htmlunit.javascript.host.Window;
  46. import com.gargoylesoftware.htmlunit.xml.XmlPage;
  47. /**
  48. * An array of elements. Used for the element arrays returned by <tt>document.all</tt>,
  49. * <tt>document.all.tags('x')</tt>, <tt>document.forms</tt>, <tt>window.frames</tt>, etc.
  50. * Note that this class must not be used for collections that can be modified, for example
  51. * <tt>map.areas</tt> and <tt>select.options</tt>.
  52. * <br>
  53. * This class (like all classes in this package) is specific for the JavaScript engine.
  54. * Users of HtmlUnit shouldn't use it directly.
  55. *
  56. * @version $Revision: 5864 $
  57. * @author Daniel Gredler
  58. * @author Marc Guillemot
  59. * @author Chris Erskine
  60. * @author Ahmed Ashour
  61. */
  62. public class HTMLCollection extends SimpleScriptable implements Function, NodeList {
  63. private static final long serialVersionUID = 4049916048017011764L;
  64. private static final Log LOG = LogFactory.getLog(HTMLCollection.class);
  65. private String xpath_;
  66. private DomNode node_;
  67. private boolean avoidObjectDetection_ = false;
  68. /**
  69. * The transformer used to get the element to return from the HTML element.
  70. * It returns the HTML element itself except for frames where it returns the nested window.
  71. */
  72. private Transformer transformer_;
  73. /**
  74. * Cache collection elements when possible, so as to avoid expensive XPath expression evaluations.
  75. */
  76. private List<Object> cachedElements_;
  77. /**
  78. * IE provides a way of enumerating through some element collections; this counter supports that functionality.
  79. */
  80. private int currentIndex_ = 0;
  81. /**
  82. * Creates an instance. JavaScript objects must have a default constructor.
  83. * Don't call.
  84. */
  85. @Deprecated
  86. public HTMLCollection() {
  87. // Empty.
  88. }
  89. /**
  90. * Creates an instance.
  91. * @param parentScope parent scope
  92. */
  93. public HTMLCollection(final DomNode parentScope) {
  94. this(parentScope.getScriptObject());
  95. }
  96. /**
  97. * Creates an instance.
  98. * @param parentScope parent scope
  99. */
  100. public HTMLCollection(final ScriptableObject parentScope) {
  101. setParentScope(parentScope);
  102. setPrototype(getPrototype(getClass()));
  103. }
  104. /**
  105. * Constructs an instance with an initial cache value.
  106. * @param parentScope the parent scope, on which we listen for changes
  107. * @param initialElements the initial content for the cache
  108. */
  109. HTMLCollection(final DomNode parentScope, final List<?> initialElements) {
  110. this(parentScope);
  111. init(parentScope, null);
  112. cachedElements_ = new ArrayList<Object>(initialElements);
  113. }
  114. /**
  115. * Only needed to make collections like <tt>document.all</tt> available but "invisible" when simulating Firefox.
  116. * {@inheritDoc}
  117. */
  118. @Override
  119. public boolean avoidObjectDetection() {
  120. return avoidObjectDetection_;
  121. }
  122. /**
  123. * @param newValue the new value
  124. */
  125. public void setAvoidObjectDetection(final boolean newValue) {
  126. avoidObjectDetection_ = newValue;
  127. }
  128. /**
  129. * Initializes the content of this collection. The elements will be "calculated" at each
  130. * access using the specified XPath expression, applied to the specified node.
  131. * @param node the node to serve as root for the XPath expression
  132. * @param xpath the XPath expression which determines the elements of the collection
  133. */
  134. public void init(final DomNode node, final String xpath) {
  135. init(node, xpath, NOPTransformer.INSTANCE);
  136. }
  137. /**
  138. * Initializes the content of this collection. The elements will be "calculated" at each
  139. * access using the specified XPath expression, applied to the specified node, and
  140. * transformed using the specified transformer.
  141. * @param node the node to serve as root for the XPath expression
  142. * @param xpath the XPath expression which determines the elements of the collection
  143. * @param transformer the transformer enabling the retrieval of the expected objects from
  144. * the results of the XPath evaluation
  145. */
  146. public void init(final DomNode node, final String xpath, final Transformer transformer) {
  147. if (node != null) {
  148. node_ = node;
  149. xpath_ = xpath;
  150. transformer_ = transformer;
  151. final DomHtmlAttributeChangeListenerImpl listener = new DomHtmlAttributeChangeListenerImpl();
  152. node_.addDomChangeListener(listener);
  153. if (node_ instanceof HtmlElement) {
  154. ((HtmlElement) node_).addHtmlAttributeChangeListener(listener);
  155. cachedElements_ = null;
  156. }
  157. }
  158. }
  159. /**
  160. * Initializes the collection. The elements will be "calculated" as the children of the node.
  161. * @param node the node to grab children from
  162. */
  163. public void initFromChildren(final DomNode node) {
  164. if (node != null) {
  165. node_ = node;
  166. final DomHtmlAttributeChangeListenerImpl listener = new DomHtmlAttributeChangeListenerImpl();
  167. node_.addDomChangeListener(listener);
  168. if (node_ instanceof HtmlElement) {
  169. ((HtmlElement) node_).addHtmlAttributeChangeListener(listener);
  170. cachedElements_ = null;
  171. }
  172. }
  173. transformer_ = NOPTransformer.INSTANCE;
  174. }
  175. /**
  176. * {@inheritDoc}
  177. */
  178. public final Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
  179. if (args.length == 0) {
  180. throw Context.reportRuntimeError("Zero arguments; need an index or a key.");
  181. }
  182. return nullIfNotFound(getIt(args[0]));
  183. }
  184. /**
  185. * {@inheritDoc}
  186. */
  187. public final Scriptable construct(final Context cx, final Scriptable scope, final Object[] args) {
  188. return null;
  189. }
  190. /**
  191. * Private helper that retrieves the item or items corresponding to the specified
  192. * index or key.
  193. * @param o the index or key corresponding to the element or elements to return
  194. * @return the element or elements corresponding to the specified index or key
  195. */
  196. private Object getIt(final Object o) {
  197. if (o instanceof Number) {
  198. final Number n = (Number) o;
  199. final int i = n.intValue();
  200. return get(i, this);
  201. }
  202. final String key = String.valueOf(o);
  203. return get(key, this);
  204. }
  205. /**
  206. * Returns the element at the specified index, or <tt>NOT_FOUND</tt> if the index is invalid.
  207. * {@inheritDoc}
  208. */
  209. @Override
  210. public final Object get(final int index, final Scriptable start) {
  211. final HTMLCollection array = (HTMLCollection) start;
  212. final List<Object> elements = array.getElements();
  213. if (index >= 0 && index < elements.size()) {
  214. return getScriptableForElement(transformer_.transform(elements.get(index)));
  215. }
  216. return NOT_FOUND;
  217. }
  218. /**
  219. * Gets the HTML elements from cache or retrieve them at first call.
  220. * @return the list of {@link HtmlElement} contained in this collection
  221. */
  222. protected List<Object> getElements() {
  223. if (cachedElements_ == null) {
  224. cachedElements_ = computeElements();
  225. }
  226. return cachedElements_;
  227. }
  228. /**
  229. * Returns the elements whose associated host objects are available through this collection.
  230. * @return the elements whose associated host objects are available through this collection
  231. */
  232. protected List<Object> computeElements() {
  233. final List<Object> response;
  234. if (node_ != null) {
  235. if (xpath_ != null) {
  236. response = XPathUtils.getByXPath(node_, xpath_);
  237. }
  238. else {
  239. response = new ArrayList<Object>();
  240. Node node = node_.getFirstChild();
  241. while (node != null) {
  242. response.add(node);
  243. node = node.getNextSibling();
  244. }
  245. }
  246. }
  247. else {
  248. response = new ArrayList<Object>();
  249. }
  250. final boolean isXmlPage = node_ != null && node_.getOwnerDocument() instanceof XmlPage;
  251. final boolean isIE = getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_45);
  252. for (int i = 0; i < response.size(); i++) {
  253. final DomNode element = (DomNode) response.get(i);
  254. //IE: XmlPage ignores all empty text nodes
  255. if (isIE && isXmlPage && element instanceof DomText
  256. && ((DomText) element).getNodeValue().trim().length() == 0) { //and 'xml:space' is 'default'
  257. final Boolean xmlSpaceDefault = isXMLSpaceDefault(element.getParentNode());
  258. if (xmlSpaceDefault != Boolean.FALSE) {
  259. response.remove(i--);
  260. continue;
  261. }
  262. }
  263. for (DomNode parent = element.getParentNode(); parent != null;
  264. parent = parent.getParentNode()) {
  265. if (parent instanceof HtmlNoScript) {
  266. response.remove(i--);
  267. break;
  268. }
  269. }
  270. }
  271. return response;
  272. }
  273. /**
  274. * Recursively checks whether "xml:space" attribute is set to "default".
  275. * @param node node to start checking from
  276. * @return {@link Boolean#TRUE} if "default" is set, {@link Boolean#FALSE} for other value,
  277. * or null if nothing is set.
  278. */
  279. private static Boolean isXMLSpaceDefault(DomNode node) {
  280. for ( ; node instanceof DomElement; node = node.getParentNode()) {
  281. final String value = ((DomElement) node).getAttribute("xml:space");
  282. if (value.length() != 0) {
  283. if (value.equals("default")) {
  284. return Boolean.TRUE;
  285. }
  286. return Boolean.FALSE;
  287. }
  288. }
  289. return null;
  290. }
  291. /**
  292. * Returns the element or elements that match the specified key. If it is the name
  293. * of a property, the property value is returned. If it is the id of an element in
  294. * the array, that element is returned. Finally, if it is the name of an element or
  295. * elements in the array, then all those elements are returned. Otherwise,
  296. * {@link #NOT_FOUND} is returned.
  297. * {@inheritDoc}
  298. */
  299. @Override
  300. protected Object getWithPreemption(final String name) {
  301. // Test to see if we are trying to get the length of this collection?
  302. // If so return NOT_FOUND here to let the property be retrieved using the prototype
  303. if (xpath_ == null || "length".equals(name)) {
  304. return NOT_FOUND;
  305. }
  306. final List<Object> elements = getElements();
  307. CollectionUtils.transform(elements, transformer_);
  308. // See if there is an element in the element array with the specified id.
  309. for (final Object next : elements) {
  310. if (next instanceof DomElement) {
  311. final String id = ((DomElement) next).getAttribute("id");
  312. if (id != null && id.equals(name)) {
  313. if (getBrowserVersion().hasFeature(BrowserVersionFeatures.HTMLCOLLECTION_IDENTICAL_IDS)) {
  314. int totalIDs = 0;
  315. for (final Object o : elements) {
  316. if (o instanceof DomElement && name.equals(((DomElement) o).getAttribute("id"))) {
  317. totalIDs++;
  318. }
  319. }
  320. if (totalIDs > 1) {
  321. final HTMLCollectionTags collection =
  322. new HTMLCollectionTags((SimpleScriptable) getParentScope());
  323. collection.setAvoidObjectDetection(
  324. !getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_46));
  325. collection.init(node_, ".//*[@id='" + id + "']");
  326. return collection;
  327. }
  328. }
  329. if (LOG.isDebugEnabled()) {
  330. LOG.debug("Property \"" + name + "\" evaluated (by id) to " + next);
  331. }
  332. return getScriptableForElement(next);
  333. }
  334. }
  335. else if (next instanceof WebWindow) {
  336. final WebWindow window = (WebWindow) next;
  337. final String windowName = window.getName();
  338. if (windowName != null && windowName.equals(name)) {
  339. if (LOG.isDebugEnabled()) {
  340. LOG.debug("Property \"" + name + "\" evaluated (by name) to " + window);
  341. }
  342. return getScriptableForElement(window);
  343. }
  344. if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_47) && window instanceof FrameWindow
  345. && ((FrameWindow) window).getFrameElement().getAttribute("id").equals(name)) {
  346. if (LOG.isDebugEnabled()) {
  347. LOG.debug("Property \"" + name + "\" evaluated (by id) to " + window);
  348. }
  349. return getScriptableForElement(window);
  350. }
  351. }
  352. else {
  353. LOG.warn("Unrecognized type in collection: " + next + " (" + next.getClass().getName() + ")");
  354. }
  355. }
  356. // See if there are any elements in the element array with the specified name.
  357. final HTMLCollection array = new HTMLCollection(this);
  358. final String newCondition = "@name = '" + name.replaceAll("\\$", "\\\\\\$") + "'";
  359. final String xpathExpr;
  360. if (xpath_.endsWith("]")) {
  361. xpathExpr = xpath_.replaceAll("\\[([^\\]]*)\\]$", "[($1) and " + newCondition + "]");
  362. }
  363. else {
  364. xpathExpr = xpath_ + "[" + newCondition + "]";
  365. }
  366. array.init(node_, xpathExpr);
  367. final List<Object> subElements = array.getElements();
  368. if (subElements.size() > 1) {
  369. if (LOG.isDebugEnabled()) {
  370. LOG.debug("Property \"" + name + "\" evaluated (by name) to " + array + " with "
  371. + subElements.size() + " elements");
  372. }
  373. return array;
  374. }
  375. else if (subElements.size() == 1) {
  376. final Scriptable singleResult = getScriptableForElement(subElements.get(0));
  377. if (LOG.isDebugEnabled()) {
  378. LOG.debug("Property \"" + name + "\" evaluated (by name) to " + singleResult);
  379. }
  380. return singleResult;
  381. }
  382. // Nothing was found.
  383. return NOT_FOUND;
  384. }
  385. /**
  386. * Returns the length of this element array.
  387. * @return the length of this element array
  388. * @see <a href="http://msdn.microsoft.com/en-us/library/ms534101.aspx">MSDN doc</a>
  389. */
  390. public final int jsxGet_length() {
  391. return getElements().size();
  392. }
  393. /**
  394. * Returns the item or items corresponding to the specified index or key.
  395. * @param index the index or key corresponding to the element or elements to return
  396. * @return the element or elements corresponding to the specified index or key
  397. * @see <a href="http://msdn.microsoft.com/en-us/library/ms536460.aspx">MSDN doc</a>
  398. */
  399. public final Object jsxFunction_item(final Object index) {
  400. return nullIfNotFound(getIt(index));
  401. }
  402. /**
  403. * Returns the specified object, unless it is the <tt>NOT_FOUND</tt> constant, in which case <tt>null</tt>
  404. * is returned for IE.
  405. * @param object the object to return
  406. * @return the specified object, unless it is the <tt>NOT_FOUND</tt> constant, in which case <tt>null</tt>
  407. * is returned for IE.
  408. */
  409. private Object nullIfNotFound(final Object object) {
  410. if (object == NOT_FOUND) {
  411. if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_48)) {
  412. return null;
  413. }
  414. return Context.getUndefinedValue();
  415. }
  416. return object;
  417. }
  418. /**
  419. * Retrieves the item or items corresponding to the specified name (checks ids, and if
  420. * that does not work, then names).
  421. * @param name the name or id the element or elements to return
  422. * @return the element or elements corresponding to the specified name or id
  423. * @see <a href="http://msdn.microsoft.com/en-us/library/ms536634.aspx">MSDN doc</a>
  424. */
  425. public final Object jsxFunction_namedItem(final String name) {
  426. return nullIfNotFound(getIt(name));
  427. }
  428. /**
  429. * Returns the next node in the collection (supporting iteration in IE only).
  430. * @return the next node in the collection
  431. */
  432. public Object jsxFunction_nextNode() {
  433. Object nextNode;
  434. final List<Object> elements = getElements();
  435. if (currentIndex_ >= 0 && currentIndex_ < elements.size()) {
  436. nextNode = elements.get(currentIndex_);
  437. }
  438. else {
  439. nextNode = null;
  440. }
  441. currentIndex_++;
  442. return nextNode;
  443. }
  444. /**
  445. * Resets the node iterator accessed via {@link #jsxFunction_nextNode()}.
  446. */
  447. public void jsxFunction_reset() {
  448. currentIndex_ = 0;
  449. }
  450. /**
  451. * Returns all the elements in this element array that have the specified tag name.
  452. * This method returns an empty element array if there are no elements with the
  453. * specified tag name.
  454. * @param tagName the name of the tag of the elements to return
  455. * @return all the elements in this element array that have the specified tag name
  456. * @see <a href="http://msdn.microsoft.com/en-us/library/ms536776.aspx">MSDN doc</a>
  457. */
  458. public Object jsxFunction_tags(final String tagName) {
  459. final HTMLCollection array = new HTMLCollection(this);
  460. array.init(node_, xpath_ + "[name() = '" + tagName.toLowerCase() + "']");
  461. return array;
  462. }
  463. /**
  464. * {@inheritDoc}
  465. */
  466. @Override
  467. public String toString() {
  468. if (xpath_ != null) {
  469. return super.toString() + '<' + xpath_ + '>';
  470. }
  471. return super.toString();
  472. }
  473. /**
  474. * Called for the js "==".
  475. * {@inheritDoc}
  476. */
  477. @Override
  478. protected Object equivalentValues(final Object other) {
  479. if (other == this) {
  480. return Boolean.TRUE;
  481. }
  482. else if (other instanceof HTMLCollection) {
  483. final HTMLCollection otherArray = (HTMLCollection) other;
  484. if (node_ == otherArray.node_
  485. && xpath_.toString().equals(otherArray.xpath_.toString())
  486. && transformer_.equals(otherArray.transformer_)) {
  487. return Boolean.TRUE;
  488. }
  489. return NOT_FOUND;
  490. }
  491. return super.equivalentValues(other);
  492. }
  493. /**
  494. * {@inheritDoc}
  495. */
  496. @Override
  497. public boolean has(final String name, final Scriptable start) {
  498. try {
  499. final int index = Integer.parseInt(name);
  500. final List<Object> elements = getElements();
  501. CollectionUtils.transform(elements, transformer_);
  502. if (index >= 0 && index < elements.size()) {
  503. return true;
  504. }
  505. }
  506. catch (final NumberFormatException e) {
  507. // Ignore.
  508. }
  509. if (name.equals("length")) {
  510. return true;
  511. }
  512. if (!getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_49)) {
  513. final JavaScriptConfiguration jsConfig = JavaScriptConfiguration.getInstance(BrowserVersion.FIREFOX_3);
  514. for (final String functionName : jsConfig.getClassConfiguration(getClassName()).functionKeys()) {
  515. if (name.equals(functionName)) {
  516. return true;
  517. }
  518. }
  519. return false;
  520. }
  521. return getWithPreemption(name) != NOT_FOUND;
  522. }
  523. /**
  524. * {@inheritDoc}.
  525. */
  526. @Override
  527. public Object[] getIds() {
  528. final List<String> idList = new ArrayList<String>();
  529. final List<Object> elements = getElements();
  530. CollectionUtils.transform(elements, transformer_);
  531. if (!getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_50)) {
  532. final int length = getElements().size();
  533. for (int i = 0; i < length; i++) {
  534. idList.add(Integer.toString(i));
  535. }
  536. idList.add("length");
  537. final JavaScriptConfiguration jsConfig = JavaScriptConfiguration.getInstance(BrowserVersion.FIREFOX_3);
  538. for (final String name : jsConfig.getClassConfiguration(getClassName()).functionKeys()) {
  539. idList.add(name);
  540. }
  541. }
  542. else {
  543. idList.add("length");
  544. int index = 0;
  545. for (final Object next : elements) {
  546. if (next instanceof HtmlElement) {
  547. final HtmlElement element = (HtmlElement) next;
  548. final String name = element.getAttribute("name");
  549. if (name != HtmlElement.ATTRIBUTE_NOT_DEFINED) {
  550. idList.add(name);
  551. }
  552. else {
  553. final String id = element.getId();
  554. if (id != HtmlElement.ATTRIBUTE_NOT_DEFINED) {
  555. idList.add(id);
  556. }
  557. else {
  558. idList.add(Integer.toString(index));
  559. }
  560. }
  561. index++;
  562. }
  563. else if (next instanceof WebWindow) {
  564. final WebWindow window = (WebWindow) next;
  565. final String windowName = window.getName();
  566. if (windowName != null) {
  567. idList.add(windowName);
  568. }
  569. }
  570. else if (LOG.isDebugEnabled()) {
  571. LOG.debug("Unrecognized type in array: \"" + next.getClass().getName() + "\"");
  572. }
  573. }
  574. }
  575. return idList.toArray();
  576. }
  577. private class DomHtmlAttributeChangeListenerImpl implements DomChangeListener, HtmlAttributeChangeListener {
  578. private static final long serialVersionUID = -6690451155079053212L;
  579. /**
  580. * {@inheritDoc}
  581. */
  582. public void nodeAdded(final DomChangeEvent event) {
  583. cachedElements_ = null;
  584. }
  585. /**
  586. * {@inheritDoc}
  587. */
  588. public void nodeDeleted(final DomChangeEvent event) {
  589. cachedElements_ = null;
  590. }
  591. /**
  592. * {@inheritDoc}
  593. */
  594. public void attributeAdded(final HtmlAttributeChangeEvent event) {
  595. cachedElements_ = null;
  596. }
  597. /**
  598. * {@inheritDoc}
  599. */
  600. public void attributeRemoved(final HtmlAttributeChangeEvent event) {
  601. cachedElements_ = null;
  602. }
  603. /**
  604. * {@inheritDoc}
  605. */
  606. public void attributeReplaced(final HtmlAttributeChangeEvent event) {
  607. cachedElements_ = null;
  608. }
  609. }
  610. /**
  611. * {@inheritDoc}
  612. */
  613. public int getLength() {
  614. return jsxGet_length();
  615. }
  616. /**
  617. * {@inheritDoc}
  618. */
  619. public Node item(final int index) {
  620. return (DomNode) transformer_.transform(getElements().get(index));
  621. }
  622. /**
  623. * Gets the scriptable for the provided element that may already be the right scriptable.
  624. * @param object the object for which to get the scriptable
  625. * @return the scriptable
  626. */
  627. protected Scriptable getScriptableForElement(final Object object) {
  628. if (object instanceof Scriptable) {
  629. return (Scriptable) object;
  630. }
  631. else if (object instanceof WebWindow) {
  632. return Window.getProxy((WebWindow) object);
  633. }
  634. return getScriptableFor(object);
  635. }
  636. /**
  637. * {@inheritDoc}
  638. */
  639. @Override
  640. public String getClassName() {
  641. return "HTMLCollection";
  642. }
  643. }