All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection Maven / Gradle / Ivy
Go to download
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright (c) 2002-2011 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
* http://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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.BrowserVersionFeatures;
import com.gargoylesoftware.htmlunit.html.DomChangeEvent;
import com.gargoylesoftware.htmlunit.html.DomChangeListener;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeEvent;
import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeListener;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.configuration.JavaScriptConfiguration;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
/**
* 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.
*
* @version $Revision: 6391 $
* @author Daniel Gredler
* @author Marc Guillemot
* @author Chris Erskine
* @author Ahmed Ashour
*/
public class HTMLCollection extends SimpleScriptable implements Function, NodeList {
/**
* Cache effect of some changes.
*/
protected enum EffectOnCache {
/** No effect, cache is still valid. */
NONE,
/** Cache is not valid anymore and should be reset. */
RESET
}
private boolean avoidObjectDetection_ = false;
private String description_;
private boolean attributeChangeSensitive_ = true;
/**
* Cache collection elements when possible, so as to avoid expensive XPath expression evaluations.
*/
private List cachedElements_;
/**
* IE provides a way of enumerating through some element collections; this counter supports that functionality.
*/
private int currentIndex_ = 0;
private boolean listenerRegistered_;
/**
* Creates an instance. JavaScript objects must have a default constructor.
* Don't call.
*/
@Deprecated
public HTMLCollection() {
// Empty.
}
/**
* Creates an instance.
* @param parentScope parent scope
*/
private HTMLCollection(final ScriptableObject parentScope) {
setParentScope(parentScope);
setPrototype(getPrototype(getClass()));
}
/**
* Creates an instance.
* @param parentScope 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)
* @param description a text useful for debugging
*/
public HTMLCollection(final DomNode parentScope, final boolean attributeChangeSensitive, final String description) {
this(parentScope.getScriptObject());
setDomNode(parentScope, false);
description_ = description;
attributeChangeSensitive_ = attributeChangeSensitive;
}
/**
* Constructs an instance with an initial cache value.
* @param parentScope the parent scope, on which we listen for changes
* @param initialElements the initial content for the cache
*/
HTMLCollection(final DomNode parentScope, final List> initialElements) {
this(parentScope.getScriptObject());
cachedElements_ = new ArrayList(initialElements);
}
/**
* Gets an empty collection.
* @param window the current scope
* @return an empty collection
*/
public static HTMLCollection emptyCollection(final Window window) {
final List list = Collections.emptyList();
return new HTMLCollection(window) {
@Override
protected List getElements() {
return list;
}
};
}
/**
* Only needed to make collections like document.all available but "invisible" when simulating Firefox.
* {@inheritDoc}
*/
@Override
public boolean avoidObjectDetection() {
return avoidObjectDetection_;
}
/**
* @param newValue the new value
*/
public void setAvoidObjectDetection(final boolean newValue) {
avoidObjectDetection_ = newValue;
}
/**
* {@inheritDoc}
*/
public final Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
if (args.length == 0) {
throw Context.reportRuntimeError("Zero arguments; need an index or a key.");
}
return nullIfNotFound(getIt(args[0]));
}
/**
* {@inheritDoc}
*/
public final Scriptable construct(final Context cx, final Scriptable scope, final Object[] args) {
return null;
}
/**
* Private helper that retrieves the item or items corresponding to the specified
* index or key.
* @param o the index or key corresponding to the element or elements to return
* @return the element or elements corresponding to the specified index or key
*/
private Object getIt(final Object o) {
if (o instanceof Number) {
final Number n = (Number) o;
final int i = n.intValue();
return get(i, this);
}
final String key = String.valueOf(o);
return get(key, this);
}
/**
* Returns the element at the specified index, or NOT_FOUND if the index is invalid.
* {@inheritDoc}
*/
@Override
public final Object get(final int index, final Scriptable start) {
final HTMLCollection array = (HTMLCollection) start;
final List elements = array.getElements();
if (index >= 0 && index < elements.size()) {
return getScriptableForElement(elements.get(index));
}
return NOT_FOUND;
}
/**
* Gets the HTML elements from cache or retrieve them at first call.
* @return the list of {@link HtmlElement} contained in this collection
*/
protected List getElements() {
// a bit strange but we like to avoid sync
List cachedElements = cachedElements_;
if (cachedElements == null) {
cachedElements = computeElements();
cachedElements_ = cachedElements;
if (!listenerRegistered_) {
final DomHtmlAttributeChangeListenerImpl listener = new DomHtmlAttributeChangeListenerImpl();
final DomNode domNode = getDomNodeOrNull();
domNode.addDomChangeListener(listener);
if (attributeChangeSensitive_ && domNode instanceof HtmlElement) {
((HtmlElement) domNode).addHtmlAttributeChangeListener(listener);
}
listenerRegistered_ = true;
}
}
// maybe the cache was cleared in between
// then this returns the old state and never null
return cachedElements;
}
/**
* Returns the elements whose associated host objects are available through this collection.
* @return the elements whose associated host objects are available through this collection
*/
protected List computeElements() {
final List response = new ArrayList();
final DomNode domNode = getDomNodeOrNull();
if (domNode == null) {
return response;
}
for (final DomNode node : getCandidates()) {
if (node instanceof DomElement && isMatching(node)) {
response.add(node);
}
}
return response;
}
/**
* Gets the DOM node that have to be examined to see if they are matching.
* Default implementation looks at all descendants of reference node.
* @return the nodes
*/
protected Iterable getCandidates() {
final DomNode domNode = getDomNodeOrNull();
return domNode.getDescendants();
}
/**
* Indicates if the node should belong to the collection.
* Belongs to the refactoring effort to improve HTMLCollection's performance.
* @param node the node to test. Will be a child node of the reference node.
* @return false
here as subclasses for concrete collections should decide it.
*/
protected boolean isMatching(final DomNode node) {
return false;
}
/**
* Returns the element or elements that match the specified key. If it is the name
* of a property, the property value is returned. If it is the id of an element in
* the array, that element is returned. Finally, if it is the name of an element or
* elements in the array, then all those elements are returned. Otherwise,
* {@link #NOT_FOUND} is returned.
* {@inheritDoc}
*/
@Override
protected Object getWithPreemption(final String name) {
// Test to see if we are trying to get the length of this collection?
// If so return NOT_FOUND here to let the property be retrieved using the prototype
if (/*xpath_ == null || */"length".equals(name)) {
return NOT_FOUND;
}
final List elements = getElements();
// See if there is an element in the element array with the specified id.
final List matchingElements = new ArrayList();
for (final Object next : elements) {
if (next instanceof DomElement) {
final String id = ((DomElement) next).getAttribute("id");
if (name.equals(id)) {
if (!getBrowserVersion().hasFeature(BrowserVersionFeatures.HTMLCOLLECTION_IDENTICAL_IDS)) {
return getScriptableForElement(next);
}
matchingElements.add(next);
}
}
}
if (matchingElements.size() == 1) {
return getScriptableForElement(matchingElements.get(0));
}
else if (!matchingElements.isEmpty()) {
final HTMLCollection collection = new HTMLCollection(getDomNodeOrDie(), matchingElements);
collection.setAvoidObjectDetection(!getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_46));
return collection;
}
// no element found by id, let's search by name
for (final Object next : elements) {
if (next instanceof DomElement) {
final String nodeName = ((DomElement) next).getAttribute("name");
if (name.equals(nodeName)) {
if (!getBrowserVersion().hasFeature(BrowserVersionFeatures.HTMLCOLLECTION_IDENTICAL_IDS)) {
return getScriptableForElement(next);
}
matchingElements.add(next);
}
}
}
if (matchingElements.isEmpty()) {
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(!getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_46));
return collection;
}
/**
* Returns the length of this element array.
* @return the length of this element array
* @see MSDN doc
*/
public final int jsxGet_length() {
return getElements().size();
}
/**
* 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
*/
public final Object jsxFunction_item(final Object index) {
return nullIfNotFound(getIt(index));
}
/**
* Returns the specified object, unless it is the NOT_FOUND constant, in which case null
* is returned for IE.
* @param object the object to return
* @return the specified object, unless it is the NOT_FOUND constant, in which case null
* is returned for IE.
*/
private Object nullIfNotFound(final Object object) {
if (object == NOT_FOUND) {
if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_48)) {
return null;
}
return Context.getUndefinedValue();
}
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
*/
public final Object jsxFunction_namedItem(final String name) {
return nullIfNotFound(getIt(name));
}
/**
* Returns the next node in the collection (supporting iteration in IE only).
* @return the next node in the collection
*/
public Object jsxFunction_nextNode() {
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 #jsxFunction_nextNode()}.
*/
public void jsxFunction_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
*/
public Object jsxFunction_tags(final String tagName) {
final String tagNameLC = tagName.toLowerCase();
final HTMLCollection collection = new HTMLSubCollection(this, ".tags('" + tagName + "')") {
@Override
protected boolean isMatching(final DomNode node) {
return tagNameLC.equalsIgnoreCase(node.getLocalName());
}
};
return collection;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return description_;
}
/**
* Called for the js "==".
* {@inheritDoc}
*/
@Override
protected Object equivalentValues(final Object other) {
if (other == this) {
return Boolean.TRUE;
}
else if (other instanceof HTMLCollection) {
final HTMLCollection otherArray = (HTMLCollection) other;
final DomNode domNode = getDomNodeOrNull();
final DomNode domNodeOther = otherArray.getDomNodeOrNull();
if (getClass() == other.getClass()
&& domNode == domNodeOther
&& getElements().equals(otherArray.getElements())) {
return Boolean.TRUE;
}
return NOT_FOUND;
}
return super.equivalentValues(other);
}
/**
* {@inheritDoc}
*/
@Override
public boolean has(final String name, final Scriptable start) {
// let's Rhino work normally if current instance is the prototype
if (isPrototype()) {
return super.has(name, start);
}
try {
final int index = Integer.parseInt(name);
final List elements = getElements();
if (index >= 0 && index < elements.size()) {
return true;
}
}
catch (final NumberFormatException e) {
// Ignore.
}
if ("length".equals(name)) {
return true;
}
if (!getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_49)) {
final JavaScriptConfiguration jsConfig = JavaScriptConfiguration.getInstance(BrowserVersion.FIREFOX_3_6);
for (final String functionName : jsConfig.getClassConfiguration(getClassName()).functionKeys()) {
if (name.equals(functionName)) {
return true;
}
}
return false;
}
return getWithPreemption(name) != NOT_FOUND;
}
/**
* {@inheritDoc}.
*/
@Override
public Object[] getIds() {
// let's Rhino work normally if current instance is the prototype
if (isPrototype()) {
return super.getIds();
}
final List idList = new ArrayList();
final List elements = getElements();
if (!getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_50)) {
final int length = elements.size();
for (int i = 0; i < length; i++) {
idList.add(Integer.toString(i));
}
idList.add("length");
final JavaScriptConfiguration jsConfig = JavaScriptConfiguration.getInstance(BrowserVersion.FIREFOX_3_6);
for (final String name : jsConfig.getClassConfiguration(getClassName()).functionKeys()) {
idList.add(name);
}
}
else {
idList.add("length");
addElementIds(idList, elements);
}
return idList.toArray();
}
private boolean isPrototype() {
return !(getPrototype() instanceof HTMLCollection);
}
/**
* Adds the ids of the collection's elements to the idList.
* @param idList the list to add the ids to
* @param elements the collection's elements
*/
protected void addElementIds(final List idList, final List elements) {
int index = 0;
for (final Object next : elements) {
final HtmlElement element = (HtmlElement) next;
final String name = element.getAttribute("name");
if (name != DomElement.ATTRIBUTE_NOT_DEFINED) {
idList.add(name);
}
else {
final String id = element.getId();
if (id != DomElement.ATTRIBUTE_NOT_DEFINED) {
idList.add(id);
}
else {
idList.add(Integer.toString(index));
}
}
index++;
}
}
private class DomHtmlAttributeChangeListenerImpl implements DomChangeListener, HtmlAttributeChangeListener {
/**
* {@inheritDoc}
*/
public void nodeAdded(final DomChangeEvent event) {
cachedElements_ = null;
}
/**
* {@inheritDoc}
*/
public void nodeDeleted(final DomChangeEvent event) {
cachedElements_ = null;
}
/**
* {@inheritDoc}
*/
public void attributeAdded(final HtmlAttributeChangeEvent event) {
handleChangeOnCache(getEffectOnCache(event));
}
/**
* {@inheritDoc}
*/
public void attributeRemoved(final HtmlAttributeChangeEvent event) {
handleChangeOnCache(getEffectOnCache(event));
}
/**
* {@inheritDoc}
*/
public void attributeReplaced(final HtmlAttributeChangeEvent event) {
if (attributeChangeSensitive_) {
handleChangeOnCache(getEffectOnCache(event));
}
}
private void handleChangeOnCache(final EffectOnCache effectOnCache) {
if (EffectOnCache.NONE == effectOnCache) {
return;
}
else if (EffectOnCache.RESET == effectOnCache) {
cachedElements_ = null;
}
}
}
/**
* {@inheritDoc}
*/
public int getLength() {
return jsxGet_length();
}
/**
* Gets the effect of the change on an attribute of the reference node
* on this collection's cache.
* @param event the change event
* @return the effect on cache
*/
protected EffectOnCache getEffectOnCache(final HtmlAttributeChangeEvent event) {
return EffectOnCache.RESET;
}
/**
* {@inheritDoc}
*/
public Node item(final int index) {
return (DomNode) getElements().get(index);
}
/**
* Gets the scriptable for the provided element that may already be the right scriptable.
* @param object the object for which to get the scriptable
* @return the scriptable
*/
protected Scriptable getScriptableForElement(final Object object) {
if (object instanceof Scriptable) {
return (Scriptable) object;
}
return getScriptableFor(object);
}
/**
* {@inheritDoc}
*/
@Override
public String getClassName() {
return "HTMLCollection";
}
}
class HTMLSubCollection extends HTMLCollection {
private final HTMLCollection mainCollection_;
public HTMLSubCollection(final HTMLCollection mainCollection, final String subDescription) {
super(mainCollection.getDomNodeOrDie(), false, mainCollection.toString() + subDescription);
mainCollection_ = mainCollection;
}
@Override
protected List computeElements() {
final List list = new ArrayList();
for (final Object o : mainCollection_.getElements()) {
if (isMatching((DomNode) o)) {
list.add(o);
}
}
return list;
}
}