com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2021 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gargoylesoftware.htmlunit.javascript.host.html;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLCOLLECTION_ITEM_SUPPORTS_DOUBLE_INDEX_ALSO;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLCOLLECTION_ITEM_SUPPORTS_ID_SEARCH_ALSO;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLCOLLECTION_NAMED_ITEM_ID_FIRST;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLCOLLECTION_SUPPORTS_PARANTHESES;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.host.dom.AbstractList;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
/**
* An array of elements. Used for the element arrays returned by document.all,
* document.all.tags('x'), document.forms, window.frames, etc.
* Note that this class must not be used for collections that can be modified, for example
* map.areas and select.options.
*
* This class (like all classes in this package) is specific for the JavaScript engine.
* Users of HtmlUnit shouldn't use it directly.
*
* @author Daniel Gredler
* @author Marc Guillemot
* @author Chris Erskine
* @author Ahmed Ashour
* @author Frank Danek
* @author Ronald Brill
*/
@JsxClass
public class HTMLCollection extends AbstractList {
/**
* IE provides a way of enumerating through some element collections; this counter supports that functionality.
*/
private int currentIndex_;
/**
* Creates an instance.
*/
@JsxConstructor({CHROME, EDGE, FF, FF78})
public HTMLCollection() {
}
/**
* Creates an instance.
* @param domNode parent scope
* @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
* of a descendant node of parentScope changes (attribute added, modified or removed)
*/
public HTMLCollection(final DomNode domNode, final boolean attributeChangeSensitive) {
super(domNode, attributeChangeSensitive);
}
/**
* Constructs an instance with an initial cache value.
* @param domNode the parent scope, on which we listen for changes
* @param initialElements the initial content for the cache
*/
HTMLCollection(final DomNode domNode, final List initialElements) {
super(domNode, initialElements);
}
/**
* Gets an empty collection.
* @param domNode the DOM node
* @return an empty collection
*/
public static HTMLCollection emptyCollection(final DomNode domNode) {
final List list = Collections.emptyList();
return new HTMLCollection(domNode, false) {
@Override
public List getElements() {
return list;
}
};
}
/**
* {@inheritDoc}
*/
@Override
protected AbstractList create(final DomNode parentScope, final List initialElements) {
return new HTMLCollection(parentScope, initialElements);
}
/**
* {@inheritDoc}
*/
@Override
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
if (supportsParentheses()) {
return super.call(cx, scope, thisObj, args);
}
throw ScriptRuntime.typeError("HTMLCollection does nont support function like access");
}
/**
* Is parentheses supported.
*
* @return true or false
*/
protected boolean supportsParentheses() {
return getBrowserVersion().hasFeature(HTMLCOLLECTION_SUPPORTS_PARANTHESES);
}
/**
* {@inheritDoc}
*/
@Override
protected Object getWithPreemptionByName(final String name, final List elements) {
final List matchingElements = new ArrayList<>();
final boolean searchName = isGetWithPreemptionSearchName();
for (final DomNode next : elements) {
if (next instanceof DomElement
&& (searchName || next instanceof HtmlInput || next instanceof HtmlForm)) {
final String nodeName = ((DomElement) next).getAttributeDirect("name");
if (name.equals(nodeName)) {
matchingElements.add(next);
}
}
}
if (matchingElements.isEmpty()) {
if (getBrowserVersion().hasFeature(HTMLCOLLECTION_ITEM_SUPPORTS_DOUBLE_INDEX_ALSO)) {
final Double doubleValue = Context.toNumber(name);
if (!doubleValue.isNaN()) {
final Object object = get(doubleValue.intValue(), this);
if (object != NOT_FOUND) {
return object;
}
}
}
return NOT_FOUND;
}
else if (matchingElements.size() == 1) {
return getScriptableForElement(matchingElements.get(0));
}
// many elements => build a sub collection
final DomNode domNode = getDomNodeOrNull();
final HTMLCollection collection = new HTMLCollection(domNode, matchingElements);
collection.setAvoidObjectDetection(true);
return collection;
}
/**
* Returns whether {@link #getWithPreemption(String)} should search by name or not.
* @return whether {@link #getWithPreemption(String)} should search by name or not
*/
protected boolean isGetWithPreemptionSearchName() {
return true;
}
/**
* Returns the item or items corresponding to the specified index or key.
* @param index the index or key corresponding to the element or elements to return
* @return the element or elements corresponding to the specified index or key
* @see MSDN doc
*/
@Override
public Object item(final Object index) {
if (index instanceof String && getBrowserVersion().hasFeature(HTMLCOLLECTION_ITEM_SUPPORTS_ID_SEARCH_ALSO)) {
final String name = (String) index;
final Object result = namedItem(name);
return result;
}
int idx = 0;
final Double doubleValue = Context.toNumber(index);
if (!doubleValue.isNaN()) {
idx = doubleValue.intValue();
}
final Object object = get(idx, this);
if (object == NOT_FOUND) {
return null;
}
return object;
}
/**
* Retrieves the item or items corresponding to the specified name (checks ids, and if
* that does not work, then names).
* @param name the name or id the element or elements to return
* @return the element or elements corresponding to the specified name or id
* @see MSDN doc
*/
@JsxFunction
public Object namedItem(final String name) {
final List elements = getElements();
final BrowserVersion browserVersion = getBrowserVersion();
if (browserVersion.hasFeature(HTMLCOLLECTION_NAMED_ITEM_ID_FIRST)) {
for (final Object next : elements) {
if (next instanceof DomElement) {
final DomElement elem = (DomElement) next;
final String id = elem.getId();
if (name.equals(id)) {
return getScriptableForElement(elem);
}
}
}
}
for (final Object next : elements) {
if (next instanceof DomElement) {
final DomElement elem = (DomElement) next;
final String nodeName = elem.getAttributeDirect("name");
if (name.equals(nodeName)) {
return getScriptableForElement(elem);
}
final String id = elem.getId();
if (name.equals(id)) {
return getScriptableForElement(elem);
}
}
}
return null;
}
/**
* Returns the next node in the collection (supporting iteration in IE only).
* @return the next node in the collection
*/
@JsxFunction(IE)
public Object nextNode() {
final Object nextNode;
final List elements = getElements();
if (currentIndex_ >= 0 && currentIndex_ < elements.size()) {
nextNode = elements.get(currentIndex_);
}
else {
nextNode = null;
}
currentIndex_++;
return nextNode;
}
/**
* Resets the node iterator accessed via {@link #nextNode()}.
*/
@JsxFunction(IE)
public void reset() {
currentIndex_ = 0;
}
/**
* Returns all the elements in this element array that have the specified tag name.
* This method returns an empty element array if there are no elements with the
* specified tag name.
* @param tagName the name of the tag of the elements to return
* @return all the elements in this element array that have the specified tag name
* @see MSDN doc
*/
@JsxFunction(IE)
public Object tags(final String tagName) {
final HTMLCollection collection = new HTMLSubCollection(this) {
@Override
protected boolean isMatching(final DomNode node) {
return tagName.equalsIgnoreCase(node.getLocalName());
}
};
return collection;
}
}
class HTMLSubCollection extends HTMLCollection {
private final HTMLCollection mainCollection_;
HTMLSubCollection(final HTMLCollection mainCollection) {
super(mainCollection.getDomNodeOrDie(), false);
mainCollection_ = mainCollection;
}
@Override
protected List computeElements() {
final List list = new ArrayList<>();
for (final DomNode o : mainCollection_.getElements()) {
if (isMatching(o)) {
list.add(o);
}
}
return list;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy