com.gargoylesoftware.htmlunit.javascript.host.Node Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client-compiler-deps Show documentation
Show all versions of vaadin-client-compiler-deps Show documentation
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;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.Interpreter;
import net.sourceforge.htmlunit.corejs.javascript.JavaScriptException;
import net.sourceforge.htmlunit.corejs.javascript.RhinoException;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import com.gargoylesoftware.htmlunit.BrowserVersionFeatures;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomText;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLHtmlElement;
import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLSerializer;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
/**
* The JavaScript object "Node" which is the base class for all DOM
* objects. This will typically wrap an instance of {@link DomNode}.
*
* @version $Revision: 6370 $
* @author Mike Bowler
* @author David K. Taylor
* @author Barnaby Court
* @author Christian Sell
* @author George Murnock
* @author Chris Erskine
* @author Bruce Faulkner
* @author Ahmed Ashour
*/
public class Node extends SimpleScriptable {
/** "Live" child nodes collection; has to be a member to have equality (==) working. */
private HTMLCollection childNodes_;
private EventListenersContainer eventListenersContainer_;
/** @see org.w3c.dom.Node#ELEMENT_NODE */
public static final short ELEMENT_NODE = org.w3c.dom.Node.ELEMENT_NODE;
/** @see org.w3c.dom.Node#ATTRIBUTE_NODE */
public static final short ATTRIBUTE_NODE = org.w3c.dom.Node.ATTRIBUTE_NODE;
/** @see org.w3c.dom.Node#TEXT_NODE */
public static final short TEXT_NODE = org.w3c.dom.Node.TEXT_NODE;
/** @see org.w3c.dom.Node#CDATA_SECTION_NODE */
public static final short CDATA_SECTION_NODE = org.w3c.dom.Node.CDATA_SECTION_NODE;
/** @see org.w3c.dom.Node#ENTITY_REFERENCE_NODE */
public static final short ENTITY_REFERENCE_NODE = org.w3c.dom.Node.ENTITY_REFERENCE_NODE;
/** @see org.w3c.dom.Node#ENTITY_NODE */
public static final short ENTITY_NODE = org.w3c.dom.Node.ENTITY_NODE;
/** @see org.w3c.dom.Node#PROCESSING_INSTRUCTION_NODE */
public static final short PROCESSING_INSTRUCTION_NODE = org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE;
/** @see org.w3c.dom.Node#COMMENT_NODE */
public static final short COMMENT_NODE = org.w3c.dom.Node.COMMENT_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_NODE */
public static final short DOCUMENT_NODE = org.w3c.dom.Node.DOCUMENT_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_TYPE_NODE */
public static final short DOCUMENT_TYPE_NODE = org.w3c.dom.Node.DOCUMENT_TYPE_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE */
public static final short DOCUMENT_FRAGMENT_NODE = org.w3c.dom.Node.DOCUMENT_FRAGMENT_NODE;
/** @see org.w3c.dom.Node#NOTATION_NODE */
public static final short NOTATION_NODE = org.w3c.dom.Node.NOTATION_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_DISCONNECTED */
public static final short DOCUMENT_POSITION_DISCONNECTED = org.w3c.dom.Node.DOCUMENT_POSITION_DISCONNECTED;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_PRECEDING */
public static final short DOCUMENT_POSITION_PRECEDING = org.w3c.dom.Node.DOCUMENT_POSITION_PRECEDING;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_FOLLOWING */
public static final short DOCUMENT_POSITION_FOLLOWING = org.w3c.dom.Node.DOCUMENT_POSITION_FOLLOWING;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINS */
public static final short DOCUMENT_POSITION_CONTAINS = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINS;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINED_BY */
public static final short DOCUMENT_POSITION_CONTAINED_BY = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINED_BY;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC */
public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
= org.w3c.dom.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
/**
* Creates an instance.
*/
public Node() {
// Empty.
}
/**
* Gets the JavaScript property "nodeType" for the current node.
* @return the node type
*/
public short jsxGet_nodeType() {
return this.getDomNodeOrDie().getNodeType();
}
/**
* Gets the JavaScript property "nodeName" for the current node.
* @return the node name
*/
public String jsxGet_nodeName() {
return this.getDomNodeOrDie().getNodeName();
}
/**
* Gets the JavaScript property "nodeValue" for the current node.
* @return the node value
*/
public String jsxGet_nodeValue() {
return this.getDomNodeOrDie().getNodeValue();
}
/**
* Sets the JavaScript property "nodeValue" for the current node.
* @param newValue the new node value
*/
public void jsxSet_nodeValue(final String newValue) {
this.getDomNodeOrDie().setNodeValue(newValue);
}
/**
* Adds a DOM node to the node.
* @param childObject the node to add to this node
* @return the newly added child node
*/
public Object jsxFunction_appendChild(final Object childObject) {
Object appendedChild = null;
if (childObject instanceof Node) {
final Node childNode = (Node) childObject;
// is the node allowed here?
if (!isNodeInsertable(childNode)) {
if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_117)) {
return childObject; // IE silently ignores it
}
throw asJavaScriptException(
new DOMException("Node cannot be inserted at the specified point in the hierarchy",
DOMException.HIERARCHY_REQUEST_ERR));
}
// Get XML node for the DOM node passed in
final DomNode childDomNode = childNode.getDomNodeOrDie();
// Get the parent XML node that the child should be added to.
final DomNode parentNode = getDomNodeOrDie();
// Append the child to the parent node
parentNode.appendChild(childDomNode);
appendedChild = childObject;
//if the parentNode has null parentNode in IE,
//create a DocumentFragment to be the parentNode's parentNode.
if (!(parentNode instanceof SgmlPage)
&& !(this instanceof DocumentFragment) && parentNode.getParentNode() == null
&& getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_118)) {
final DomDocumentFragment fragment = parentNode.getPage().createDomDocumentFragment();
fragment.appendChild(parentNode);
}
}
return appendedChild;
}
private RhinoException asJavaScriptException(final DOMException exception) {
exception.setPrototype(getWindow().getPrototype(exception.getClass()));
exception.setParentScope(getWindow());
// get current line and file name
// this method can only be used in interpreted mode. If one day we choose to use compiled mode,
// then we'll have to find an other way here.
final String fileName;
final int lineNumber;
if (Context.getCurrentContext().getOptimizationLevel() == -1) {
final int[] linep = new int[1];
final String sourceName = new Interpreter().getSourcePositionFromStack(Context.getCurrentContext(), linep);
fileName = sourceName.replaceFirst("script in (.*) from .*", "$1");
lineNumber = linep[0];
}
else {
throw new Error("HtmlUnit not ready to run in compiled mode");
}
exception.setLocation(fileName, lineNumber);
return new JavaScriptException(exception, fileName, lineNumber);
}
/**
* Indicates if the node can be inserted.
* @param childObject the node
* @return false
if it is not allowed here
*/
private boolean isNodeInsertable(final Node childObject) {
return !(childObject instanceof HTMLHtmlElement);
}
/**
* Clones this node.
* @param deep if true, recursively clones all descendants
* @return the newly cloned node
*/
public Object jsxFunction_cloneNode(final boolean deep) {
final DomNode domNode = getDomNodeOrDie();
final DomNode clonedNode = domNode.cloneNode(deep);
final Node jsClonedNode = getJavaScriptNode(clonedNode);
if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_119)) {
// need to copy the event listener when they exist
copyEventListenersWhenNeeded(domNode, clonedNode);
}
return jsClonedNode;
}
private void copyEventListenersWhenNeeded(final DomNode domNode, final DomNode clonedNode) {
final Node jsNode = (Node) domNode.getScriptObject();
if (jsNode != null) {
final Node jsClonedNode = getJavaScriptNode(clonedNode);
jsClonedNode.getEventListenersContainer().copyFrom(jsNode.getEventListenersContainer());
}
// look through the children
DomNode child = domNode.getFirstChild();
DomNode clonedChild = clonedNode.getFirstChild();
while (child != null && clonedChild != null) {
copyEventListenersWhenNeeded(child, clonedChild);
child = child.getNextSibling();
clonedChild = clonedChild.getNextSibling();
}
}
/**
* Add a DOM node as a child to this node before the referenced node.
* If the referenced node is null, append to the end.
* @param context the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
* @return the newly added child node
*/
public static Object jsxFunction_insertBefore(
final Context context, final Scriptable thisObj, final Object[] args, final Function function) {
return ((Node) thisObj).jsxFunction_insertBefore(args);
}
/**
* Add a DOM node as a child to this node before the referenced node.
* If the referenced node is null, append to the end.
* @param args the arguments
* @return the newly added child node
*/
protected Object jsxFunction_insertBefore(final Object[] args) {
final Object newChildObject = args[0];
final Object refChildObject;
if (args.length > 1) {
refChildObject = args[1];
}
else {
refChildObject = Undefined.instance;
}
Object appendedChild = null;
if (newChildObject instanceof Node) {
final Node newChild = (Node) newChildObject;
final DomNode newChildNode = newChild.getDomNodeOrDie();
// is the node allowed here?
if (!isNodeInsertable(newChild)) {
if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_120)) {
return newChildNode; // IE silently ignores it
}
throw Context.reportRuntimeError("Node cannot be inserted at the specified point in the hierarchy");
}
if (newChildNode instanceof DomDocumentFragment) {
final DomDocumentFragment fragment = (DomDocumentFragment) newChildNode;
for (final DomNode child : fragment.getChildren()) {
jsxFunction_insertBefore(new Object[] {child.getScriptObject(), refChildObject});
}
return newChildObject;
}
final DomNode refChildNode;
// IE accepts non standard calls with only one arg
if (refChildObject == Undefined.instance) {
if (getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_121)) {
if (args.length > 1) {
throw Context.reportRuntimeError("Invalid argument.");
}
refChildNode = null;
}
else {
if (args.length == 2) {
refChildNode = null;
}
else {
throw Context.reportRuntimeError("insertBefore: not enough arguments");
}
}
}
else if (refChildObject != null) {
refChildNode = ((Node) refChildObject).getDomNodeOrDie();
}
else {
refChildNode = null;
}
final DomNode domNode = getDomNodeOrDie();
// Append the child to the parent node
if (refChildNode != null) {
refChildNode.insertBefore(newChildNode);
appendedChild = newChildObject;
}
else {
domNode.appendChild(newChildNode);
appendedChild = newChildObject;
}
// if parentNode is null in IE, create a DocumentFragment to be the parentNode
if (domNode.getParentNode() == null
&& getWindow().getWebWindow().getWebClient().getBrowserVersion()
.hasFeature(BrowserVersionFeatures.GENERATED_122)) {
final DomDocumentFragment fragment = domNode.getPage().createDomDocumentFragment();
fragment.appendChild(domNode);
}
}
return appendedChild;
}
/**
* This method provides a way to determine whether two Node references returned by
* the implementation reference the same object.
* When two Node references are references to the same object, even if through a proxy,
* the references may be used completely interchangeably, such that all attributes
* have the same values and calling the same DOM method on either reference always has exactly the same effect.
*
* @param other the node to test against
*
* @return whether this node is the same node as the given one
*/
public boolean jsxFunction_isSameNode(final Object other) {
return other == this;
}
/**
* Removes a DOM node from this node.
* @param childObject the node to remove from this node
* @return the removed child node
*/
public Object jsxFunction_removeChild(final Object childObject) {
Object removedChild = null;
if (childObject instanceof Node) {
// Get XML node for the DOM node passed in
final DomNode childNode = ((Node) childObject).getDomNodeOrDie();
// Remove the child from the parent node
childNode.remove();
removedChild = childObject;
}
return removedChild;
}
/**
* Returns whether this node has any children.
* @return boolean true if this node has any children, false otherwise
*/
public boolean jsxFunction_hasChildNodes() {
return this.getDomNodeOrDie().getChildren().iterator().hasNext();
}
/**
* Returns the child nodes of the current element.
* @return the child nodes of the current element
*/
public HTMLCollection jsxGet_childNodes() {
if (childNodes_ == null) {
final DomNode node = getDomNodeOrDie();
final boolean isXmlPage = node.getOwnerDocument() instanceof XmlPage;
final boolean isIE = getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_45);
final Boolean xmlSpaceDefault = isXMLSpaceDefault(node);
final boolean skipEmptyTextNode = isIE && isXmlPage && !Boolean.FALSE.equals(xmlSpaceDefault);
childNodes_ = new HTMLCollection(node, false, "Node.childNodes") {
@Override
protected List