org.htmlunit.javascript.host.dom.Node Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2002-2024 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 org.htmlunit.javascript.host.dom;
import static org.htmlunit.BrowserVersionFeatures.JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG;
import static org.htmlunit.BrowserVersionFeatures.JS_NODE_INSERT_BEFORE_REF_OPTIONAL;
import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
import static org.htmlunit.javascript.configuration.SupportedBrowser.IE;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import org.htmlunit.SgmlPage;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.Function;
import org.htmlunit.corejs.javascript.Interpreter;
import org.htmlunit.corejs.javascript.JavaScriptException;
import org.htmlunit.corejs.javascript.RhinoException;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.Undefined;
import org.htmlunit.html.DomDocumentFragment;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.HtmlElement;
import org.htmlunit.html.HtmlInlineFrame;
import org.htmlunit.javascript.HtmlUnitScriptable;
import org.htmlunit.javascript.JavaScriptEngine;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstant;
import org.htmlunit.javascript.configuration.JsxConstructor;
import org.htmlunit.javascript.configuration.JsxFunction;
import org.htmlunit.javascript.configuration.JsxGetter;
import org.htmlunit.javascript.configuration.JsxSetter;
import org.htmlunit.javascript.host.Element;
import org.htmlunit.javascript.host.NamedNodeMap;
import org.htmlunit.javascript.host.Window;
import org.htmlunit.javascript.host.event.EventTarget;
import org.htmlunit.javascript.host.html.HTMLCollection;
import org.htmlunit.javascript.host.html.HTMLDocument;
import org.htmlunit.javascript.host.html.HTMLHtmlElement;
/**
* The JavaScript object {@code Node} which is the base class for all DOM
* objects. This will typically wrap an instance of {@link DomNode}.
*
* @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
* @author Ronald Brill
* @author Frank Danek
*/
@JsxClass
public class Node extends EventTarget {
/** @see org.w3c.dom.Node#ELEMENT_NODE */
@JsxConstant
public static final int ELEMENT_NODE = org.w3c.dom.Node.ELEMENT_NODE;
/** @see org.w3c.dom.Node#ATTRIBUTE_NODE */
@JsxConstant
public static final int ATTRIBUTE_NODE = org.w3c.dom.Node.ATTRIBUTE_NODE;
/** @see org.w3c.dom.Node#TEXT_NODE */
@JsxConstant
public static final int TEXT_NODE = org.w3c.dom.Node.TEXT_NODE;
/** @see org.w3c.dom.Node#CDATA_SECTION_NODE */
@JsxConstant
public static final int CDATA_SECTION_NODE = org.w3c.dom.Node.CDATA_SECTION_NODE;
/** @see org.w3c.dom.Node#ENTITY_REFERENCE_NODE */
@JsxConstant
public static final int ENTITY_REFERENCE_NODE = org.w3c.dom.Node.ENTITY_REFERENCE_NODE;
/** @see org.w3c.dom.Node#ENTITY_NODE */
@JsxConstant
public static final int ENTITY_NODE = org.w3c.dom.Node.ENTITY_NODE;
/** @see org.w3c.dom.Node#PROCESSING_INSTRUCTION_NODE */
@JsxConstant
public static final int PROCESSING_INSTRUCTION_NODE = org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE;
/** @see org.w3c.dom.Node#COMMENT_NODE */
@JsxConstant
public static final int COMMENT_NODE = org.w3c.dom.Node.COMMENT_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_NODE */
@JsxConstant
public static final int DOCUMENT_NODE = org.w3c.dom.Node.DOCUMENT_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_TYPE_NODE */
@JsxConstant
public static final int DOCUMENT_TYPE_NODE = org.w3c.dom.Node.DOCUMENT_TYPE_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE */
@JsxConstant
public static final int DOCUMENT_FRAGMENT_NODE = org.w3c.dom.Node.DOCUMENT_FRAGMENT_NODE;
/** @see org.w3c.dom.Node#NOTATION_NODE */
@JsxConstant
public static final int NOTATION_NODE = org.w3c.dom.Node.NOTATION_NODE;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_DISCONNECTED */
@JsxConstant
public static final int DOCUMENT_POSITION_DISCONNECTED = org.w3c.dom.Node.DOCUMENT_POSITION_DISCONNECTED;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_PRECEDING */
@JsxConstant
public static final int DOCUMENT_POSITION_PRECEDING = org.w3c.dom.Node.DOCUMENT_POSITION_PRECEDING;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_FOLLOWING */
@JsxConstant
public static final int DOCUMENT_POSITION_FOLLOWING = org.w3c.dom.Node.DOCUMENT_POSITION_FOLLOWING;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINS */
@JsxConstant
public static final int DOCUMENT_POSITION_CONTAINS = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINS;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINED_BY */
@JsxConstant
public static final int DOCUMENT_POSITION_CONTAINED_BY = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINED_BY;
/** @see org.w3c.dom.Node#DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC */
@JsxConstant
public static final int DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
= org.w3c.dom.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
/** "Live" child nodes collection; has to be a member to have equality (==) working. */
private NodeList childNodes_;
/**
* Creates an instance.
*/
public Node() {
// Empty.
}
/**
* JavaScript constructor.
*/
@Override
@JsxConstructor({CHROME, EDGE, FF, FF_ESR})
public void jsConstructor() {
super.jsConstructor();
}
/**
* Gets the JavaScript property {@code nodeType} for the current node.
* @return the node type
*/
@JsxGetter
public int getNodeType() {
return getDomNodeOrDie().getNodeType();
}
/**
* Gets the JavaScript property {@code nodeName} for the current node.
* @return the node name
*/
@JsxGetter
public String getNodeName() {
return getDomNodeOrDie().getNodeName();
}
/**
* Gets the JavaScript property {@code nodeValue} for the current node.
* @return the node value
*/
@JsxGetter
public String getNodeValue() {
return getDomNodeOrDie().getNodeValue();
}
/**
* Sets the JavaScript property {@code nodeValue} for the current node.
* @param newValue the new node value
*/
@JsxSetter
public void setNodeValue(final String newValue) {
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
*/
@JsxFunction
public Object appendChild(final Object childObject) {
Object appendedChild = null;
if (childObject instanceof Node) {
final Node childNode = (Node) childObject;
// is the node allowed here?
if (!isNodeInsertable(childNode)) {
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;
initInlineFrameIfNeeded(childDomNode);
for (final HtmlElement htmlElement : childDomNode.getHtmlElementDescendants()) {
initInlineFrameIfNeeded(htmlElement);
}
}
return appendedChild;
}
/**
* If we have added a new iframe that
* had no source attribute, we have to take care the
* 'onload' handler is triggered.
*/
private static void initInlineFrameIfNeeded(final DomNode childDomNode) {
if (childDomNode instanceof HtmlInlineFrame) {
final HtmlInlineFrame frame = (HtmlInlineFrame) childDomNode;
if (DomElement.ATTRIBUTE_NOT_DEFINED == frame.getSrcAttribute()) {
frame.loadInnerPage();
}
}
}
/**
* Encapsulates the given {@link DOMException} into a Rhino-compatible exception.
*
* @param exception the exception to encapsulate
* @return the created exception
*/
protected RhinoException asJavaScriptException(final DOMException exception) {
final Window w = getWindow();
exception.setPrototype(w.getPrototype(exception.getClass()));
exception.setParentScope(w);
// 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);
}
/**
* 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 scope the scope
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
* @return the newly added child node
*/
@JsxFunction
public static Object insertBefore(final Context context, final Scriptable scope,
final Scriptable thisObj, final Object[] args, final Function function) {
return ((Node) thisObj).insertBeforeImpl(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 insertBeforeImpl(final Object[] args) {
if (args.length < 1) {
throw JavaScriptEngine.typeError(
"Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 0 present.");
}
final Object newChildObject = args[0];
final Object refChildObject;
if (args.length > 1) {
refChildObject = args[1];
}
else {
refChildObject = Undefined.instance;
}
Object insertedChild = null;
if (newChildObject instanceof Node) {
final Node newChild = (Node) newChildObject;
// is the node allowed here?
if (!isNodeInsertable(newChild)) {
throw JavaScriptEngine.constructError("ReferenceError",
"Node cannot be inserted at the specified point in the hierarchy");
}
final DomNode newChildNode = newChild.getDomNodeOrDie();
if (newChildNode instanceof DomDocumentFragment) {
final DomDocumentFragment fragment = (DomDocumentFragment) newChildNode;
for (final DomNode child : fragment.getChildren()) {
if (!isNodeInsertable(child.getScriptableObject())) {
throw JavaScriptEngine.constructError("ReferenceError",
"Node cannot be inserted at the specified point in the hierarchy");
}
}
}
// extract refChild
final DomNode refChildNode;
if (JavaScriptEngine.isUndefined(refChildObject)) {
if (args.length == 2 || getBrowserVersion().hasFeature(JS_NODE_INSERT_BEFORE_REF_OPTIONAL)) {
refChildNode = null;
}
else {
throw JavaScriptEngine.typeError(
"Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present.");
}
}
else if (refChildObject == null) {
refChildNode = null;
}
else {
refChildNode = ((Node) refChildObject).getDomNodeOrDie();
}
final DomNode domNode = getDomNodeOrDie();
try {
domNode.insertBefore(newChildNode, refChildNode);
}
catch (final org.w3c.dom.DOMException e) {
throw JavaScriptEngine.constructError("ReferenceError", e.getMessage());
}
insertedChild = newChild;
}
return insertedChild;
}
/**
* Indicates if the node can be inserted.
* @param childObject the node
* @return {@code false} if it is not allowed here
*/
private static boolean isNodeInsertable(final Node childObject) {
if (childObject instanceof HTMLHtmlElement) {
final DomNode domNode = childObject.getDomNodeOrDie();
return domNode.getPage().getDocumentElement() != domNode;
}
return true;
}
/**
* Removes the DOM node from its parent.
* @see MDN documentation
*/
protected void remove() {
getDomNodeOrDie().remove();
}
/**
* Removes a DOM node from this node.
* @param childObject the node to remove from this node
* @return the removed child node
*/
@JsxFunction
public Object removeChild(final Object childObject) {
if (!(childObject instanceof Node)) {
return null;
}
// Get XML node for the DOM node passed in
final DomNode childNode = ((Node) childObject).getDomNodeOrDie();
if (!getDomNodeOrDie().isAncestorOf(childNode)) {
throw JavaScriptEngine.throwAsScriptRuntimeEx(
new Exception("NotFoundError: Failed to execute 'removeChild' on '"
+ this + "': The node to be removed is not a child of this node."));
}
// Remove the child from the parent node
childNode.remove();
return childObject;
}
/**
* Replaces a child DOM node with another DOM node.
* @param newChildObject the node to add as a child of this node
* @param oldChildObject the node to remove as a child of this node
* @return the removed child node
*/
@JsxFunction
public Object replaceChild(final Object newChildObject, final Object oldChildObject) {
Object removedChild = null;
if (newChildObject instanceof DocumentFragment) {
final DocumentFragment fragment = (DocumentFragment) newChildObject;
Node firstNode = null;
final Node refChildObject = ((Node) oldChildObject).getNextSibling();
for (final DomNode node : fragment.getDomNodeOrDie().getChildren()) {
if (firstNode == null) {
replaceChild(node.getScriptableObject(), oldChildObject);
firstNode = node.getScriptableObject();
}
else {
insertBeforeImpl(new Object[] {node.getScriptableObject(), refChildObject});
}
}
if (firstNode == null) {
removeChild(oldChildObject);
}
removedChild = oldChildObject;
}
else if (newChildObject instanceof Node && oldChildObject instanceof Node) {
final Node newChild = (Node) newChildObject;
// is the node allowed here?
if (!isNodeInsertable(newChild)) {
throw JavaScriptEngine.reportRuntimeError(
"Node cannot be inserted at the specified point in the hierarchy");
}
// Get XML nodes for the DOM nodes passed in
final DomNode newChildNode = newChild.getDomNodeOrDie();
final DomNode oldChildNode = ((Node) oldChildObject).getDomNodeOrDie();
// Replace the old child with the new child.
oldChildNode.replace(newChildNode);
removedChild = oldChildObject;
}
return removedChild;
}
/**
* Clones this node.
* @param deep if {@code true}, recursively clones all descendants
* @return the newly cloned node
*/
@JsxFunction
public Object cloneNode(final boolean deep) {
final DomNode domNode = getDomNodeOrDie();
final DomNode clonedNode = domNode.cloneNode(deep);
return getJavaScriptNode(clonedNode);
}
/**
* Check if 2 nodes are equals.
* For detail specifications
* @see concept-node-equals
* @param other the node to compare with
* @return true or false
*/
@JsxFunction
public boolean isEqualNode(final Node other) {
if (isSameNode(other)) {
return true;
}
if (!getClassName().equals(other.getClassName())) {
return false;
}
if (this instanceof DocumentType) {
final DocumentType docType = (DocumentType) this;
final DocumentType otherDocType = (DocumentType) other;
if (!Objects.equals(docType.getName(), otherDocType.getName())
|| !Objects.equals(docType.getPublicId(), otherDocType.getPublicId())
|| !Objects.equals(docType.getSystemId(), otherDocType.getSystemId())) {
return false;
}
}
else if (this instanceof Element) {
final Element element = (Element) this;
final Element otherElement = (Element) other;
if (!Objects.equals(element.getNodeName(), otherElement.getNodeName())
|| !Objects.equals(element.getPrefix(), otherElement.getPrefix())
|| !Objects.equals(element.getLocalName(), otherElement.getLocalName())) {
return false;
}
final NamedNodeMap attributesMap = element.getAttributes();
final NamedNodeMap otherAttributesMap = otherElement.getAttributes();
if (attributesMap != null || otherAttributesMap != null) {
if (attributesMap == null || otherAttributesMap == null) {
return false;
}
final int length = attributesMap.getLength();
if (length != otherAttributesMap.getLength()) {
return false;
}
final Map name2Attributes = new HashMap<>();
for (int i = 0; i < length; i++) {
final Attr attribute = (Attr) attributesMap.item(i);
name2Attributes.put(attribute.getName(), attribute);
}
for (int i = 0; i < length; i++) {
final Attr otherAttribute = (Attr) otherAttributesMap.item(i);
final Attr attribute = name2Attributes.get(otherAttribute.getName());
if (attribute == null) {
return false;
}
if (!attribute.isEqualNode(otherAttribute)) {
return false;
}
}
}
}
else if (this instanceof Attr) {
final Attr attr = (Attr) this;
final Attr otherAttr = (Attr) other;
if (!Objects.equals(attr.getName(), otherAttr.getName())
|| !Objects.equals(attr.getLocalName(), otherAttr.getLocalName())
|| !Objects.equals(attr.getValue(), otherAttr.getValue())) {
return false;
}
}
else if (this instanceof ProcessingInstruction) {
final ProcessingInstruction instruction = (ProcessingInstruction) this;
final ProcessingInstruction otherInstruction = (ProcessingInstruction) other;
if (!Objects.equals(instruction.getTarget(), otherInstruction.getTarget())
|| !Objects.equals(instruction.getData(), otherInstruction.getData())) {
return false;
}
}
else if (this instanceof Text || this instanceof Comment) {
final CharacterData data = (CharacterData) this;
final CharacterData otherData = (CharacterData) other;
if (!Objects.equals(data.getData(), otherData.getData())) {
return false;
}
}
final NodeList childNodes = getChildNodes();
final NodeList otherChildNodes = other.getChildNodes();
if (childNodes != null || otherChildNodes != null) {
if (childNodes == null || otherChildNodes == null) {
return false;
}
final int length = childNodes.getLength();
final int otherLength = childNodes.getLength();
if (length != otherLength) {
return false;
}
for (int i = 0; i < length; i++) {
final Node childNode = (Node) childNodes.item(i);
final Node otherChildNode = (Node) otherChildNodes.item(i);
if (!childNode.isEqualNode(otherChildNode)) {
return false;
}
}
}
return true;
}
/**
* 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
*/
@JsxFunction
public boolean isSameNode(final Object other) {
return other == this;
}
/**
* Returns whether this node has any children.
* @return boolean true if this node has any children, false otherwise
*/
@JsxFunction
public boolean hasChildNodes() {
return getDomNodeOrDie().getChildren().iterator().hasNext();
}
/**
* Returns the child nodes of the current element.
* @return the child nodes of the current element
*/
@JsxGetter
public NodeList getChildNodes() {
if (childNodes_ == null) {
final DomNode node = getDomNodeOrDie();
childNodes_ = new NodeList(node, false);
childNodes_.setElementsSupplier(
(Supplier> & Serializable)
() -> {
final List response = new ArrayList<>();
for (final DomNode child : node.getChildren()) {
response.add(child);
}
return response;
});
}
return childNodes_;
}
/**
* Returns this node's parent node.
* @return this node's parent node
*/
public final Node getParent() {
return getJavaScriptNode(getDomNodeOrDie().getParentNode());
}
/**
* Gets the JavaScript property {@code parentNode} for the node that
* contains the current node.
* @return the parent node
*/
@JsxGetter
public Object getParentNode() {
return getJavaScriptNode(getDomNodeOrDie().getParentNode());
}
/**
* Gets the JavaScript property {@code nextSibling} for the node that
* contains the current node.
* @return the next sibling node or null if the current node has
* no next sibling.
*/
@JsxGetter
public Node getNextSibling() {
return getJavaScriptNode(getDomNodeOrDie().getNextSibling());
}
/**
* Gets the JavaScript property {@code previousSibling} for the node that
* contains the current node.
* @return the previous sibling node or null if the current node has
* no previous sibling.
*/
@JsxGetter
public Node getPreviousSibling() {
return getJavaScriptNode(getDomNodeOrDie().getPreviousSibling());
}
/**
* Gets the JavaScript property {@code firstChild} for the node that
* contains the current node.
* @return the first child node or null if the current node has
* no children.
*/
@JsxGetter
public Node getFirstChild() {
return getJavaScriptNode(getDomNodeOrDie().getFirstChild());
}
/**
* Gets the JavaScript property {@code lastChild} for the node that
* contains the current node.
* @return the last child node or null if the current node has
* no children.
*/
@JsxGetter
public Node getLastChild() {
return getJavaScriptNode(getDomNodeOrDie().getLastChild());
}
/**
* Gets the JavaScript node for a given DomNode.
* @param domNode the DomNode
* @return the JavaScript node or null if the DomNode was null
*/
protected Node getJavaScriptNode(final DomNode domNode) {
if (domNode == null) {
return null;
}
return (Node) getScriptableFor(domNode);
}
/**
* Returns the owner document.
* @return the document
*/
@JsxGetter
public HtmlUnitScriptable getOwnerDocument() {
final Object document = getDomNodeOrDie().getOwnerDocument();
if (document != null) {
return ((SgmlPage) document).getScriptableObject();
}
return null;
}
/**
* Returns the owner document.
* @return the document
*/
@JsxFunction({CHROME, EDGE, FF, FF_ESR})
public Object getRootNode() {
Node parent = this;
while (parent != null) {
if (parent instanceof Document || parent instanceof DocumentFragment) {
return parent;
}
parent = parent.getParent();
}
return this;
}
/**
* Compares the positions of this node and the provided node within the document.
* @param node node object that specifies the node to check
* @return how the node is positioned relatively to the reference node.
* @see DOM level 3
* @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
*/
@JsxFunction
public int compareDocumentPosition(final Object node) {
if (!(node instanceof Node)) {
throw JavaScriptEngine.reportRuntimeError("Could not convert JavaScript argument arg 0");
}
return getDomNodeOrDie().compareDocumentPosition(((Node) node).getDomNodeOrDie());
}
/**
* Merges adjacent TextNode objects to produce a normalized document object model.
*/
@JsxFunction
public void normalize() {
getDomNodeOrDie().normalize();
}
/**
* Gets the textContent attribute.
* @return the contents of this node as text
*/
@JsxGetter
public String getTextContent() {
return getDomNodeOrDie().getTextContent();
}
/**
* Replace all children elements of this element with the supplied value.
* @param value - the new value for the contents of this node
*/
@JsxSetter
public void setTextContent(final Object value) {
getDomNodeOrDie().setTextContent(value == null ? null : JavaScriptEngine.toString(value));
}
/**
* Gets the JavaScript property {@code parentElement}.
* @return the parent element
* @see #getParentNode()
*/
@JsxGetter({CHROME, EDGE, FF, FF_ESR})
public Element getParentElement() {
final Node parent = getParent();
if (!(parent instanceof Element)) {
return null;
}
return (Element) parent;
}
/**
* Returns the attributes of this XML element.
* @see Gecko DOM Reference
* @return the attributes of this XML element
*/
@JsxGetter(IE)
public Object getAttributes() {
return null;
}
/**
* Checks whether the given element is contained within this object.
* @param element element object that specifies the element to check
* @return true if the element is contained within this object
*/
@JsxFunction({CHROME, EDGE, FF, FF_ESR})
public boolean contains(final Object element) {
if (element == null || JavaScriptEngine.isUndefined(element)) {
return false;
}
if (!(element instanceof Node)) {
if (getBrowserVersion().hasFeature(JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG)) {
return false;
}
throw JavaScriptEngine.reportRuntimeError("Could not convert JavaScript argument arg 0");
}
if (getBrowserVersion().hasFeature(JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG)) {
if (element instanceof CharacterData) {
return false;
}
if (this instanceof CharacterData) {
throw JavaScriptEngine.reportRuntimeError("Function 'contains' not available for text nodes.");
}
}
for (Node parent = (Node) element; parent != null; parent = parent.getParentElement()) {
if (this == parent) {
return true;
}
}
return false;
}
/**
* Returns the Base URI as a string.
* @return the Base URI as a string
*/
@JsxGetter({CHROME, EDGE, FF, FF_ESR})
public String getBaseURI() {
return getDomNodeOrDie().getBaseURI();
}
/**
* Returns true when the current element has any attributes or not.
* @return true if an attribute is specified on this element
*/
@JsxFunction(IE)
public boolean hasAttributes() {
return getDomNodeOrDie().hasAttributes();
}
/**
* Returns the namespace prefix.
* @return the namespace prefix
*/
@JsxGetter(IE)
public Object getPrefix() {
return getDomNodeOrDie().getPrefix();
}
/**
* Returns the local name of this attribute.
* @return the local name of this attribute
*/
@JsxGetter(IE)
public Object getLocalName() {
return getDomNodeOrDie().getLocalName();
}
/**
* Returns the URI that identifies an XML namespace.
* @return the URI that identifies an XML namespace
*/
@JsxGetter(IE)
public Object getNamespaceURI() {
return getDomNodeOrDie().getNamespaceURI();
}
/**
* Returns the current number of child elements.
* @return the child element count
*/
protected int getChildElementCount() {
final DomNode domNode = getDomNodeOrDie();
if (domNode instanceof DomElement) {
return ((DomElement) domNode).getChildElementCount();
}
int counter = 0;
for (final DomNode child : getDomNodeOrDie().getChildren()) {
if (child != null) {
final HtmlUnitScriptable scriptable = child.getScriptableObject();
if (scriptable instanceof Element) {
counter++;
}
}
}
return counter;
}
/**
* Returns the first element child.
* @return the first element child
*/
protected Element getFirstElementChild() {
final DomNode domNode = getDomNodeOrDie();
if (domNode instanceof DomElement) {
final DomElement child = ((DomElement) domNode).getFirstElementChild();
if (child != null) {
return child.getScriptableObject();
}
return null;
}
for (final DomNode child : domNode.getChildren()) {
if (child != null) {
final HtmlUnitScriptable scriptable = child.getScriptableObject();
if (scriptable instanceof Element) {
return (Element) scriptable;
}
}
}
return null;
}
/**
* Returns the last element child.
* @return the last element child
*/
protected Element getLastElementChild() {
final DomNode domNode = getDomNodeOrDie();
if (domNode instanceof DomElement) {
final DomElement child = ((DomElement) getDomNodeOrDie()).getLastElementChild();
if (child != null) {
return child.getScriptableObject();
}
return null;
}
Element result = null;
for (final DomNode child : domNode.getChildren()) {
final HtmlUnitScriptable scriptable = child.getScriptableObject();
if (scriptable instanceof Element) {
result = (Element) scriptable;
}
}
return result;
}
/**
* Gets the children of the current node.
* @see MSDN documentation
* @return the child at the given position
*/
protected HTMLCollection getChildren() {
final DomNode node = getDomNodeOrDie();
final HTMLCollection childrenColl = new HTMLCollection(node, false);
childrenColl.setElementsSupplier(
(Supplier> & Serializable)
() -> {
final List children = new ArrayList<>();
for (final DomNode domNode : node.getChildNodes()) {
if (domNode instanceof DomElement) {
children.add(domNode);
}
}
return children;
});
return childrenColl;
}
/**
* Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
* just after this ChildNode.
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
*/
protected static void after(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
final DomNode parentNode = thisDomNode.getParentNode();
final DomNode nextSibling = thisDomNode.getNextSibling();
for (final Object arg : args) {
final Node node = toNodeOrTextNode((Node) thisObj, arg);
final DomNode newNode = node.getDomNodeOrDie();
if (nextSibling == null) {
parentNode.appendChild(newNode);
}
else {
nextSibling.insertBefore(newNode);
}
}
}
/**
* Inserts a set of Node objects or string objects after the last child of the Element.
* String objects are inserted as equivalent Text nodes.
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
*/
protected static void append(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
if (!(thisObj instanceof Element)) {
throw JavaScriptEngine.typeError("Illegal invocation");
}
final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
for (final Object arg : args) {
final Node node = toNodeOrTextNode((Node) thisObj, arg);
final DomNode newNode = node.getDomNodeOrDie();
thisDomNode.appendChild(newNode);
}
}
/**
* Inserts a set of Node objects or string objects before the first child of the Element.
* String objects are inserted as equivalent Text nodes.
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
*/
protected static void prepend(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
if (!(thisObj instanceof Element)) {
throw JavaScriptEngine.typeError("Illegal invocation");
}
final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
final DomNode firstChild = thisDomNode.getFirstChild();
for (final Object arg : args) {
final Node node = toNodeOrTextNode((Node) thisObj, arg);
final DomNode newNode = node.getDomNodeOrDie();
if (firstChild == null) {
thisDomNode.appendChild(newNode);
}
else {
firstChild.insertBefore(newNode);
}
}
}
/**
* Replaces the existing children of a Node with a specified new set of children.
* These can be string or Node objects.
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
*/
protected static void replaceChildren(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
if (!(thisObj instanceof Element)) {
throw JavaScriptEngine.typeError("Illegal invocation");
}
final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
thisDomNode.removeAllChildren();
for (final Object arg : args) {
final Node node = toNodeOrTextNode((Node) thisObj, arg);
thisDomNode.appendChild(node.getDomNodeOrDie());
}
}
private static Node toNodeOrTextNode(final Node thisObj, final Object obj) {
if (obj instanceof Node) {
return (Node) obj;
}
return (Node)
((HTMLDocument) thisObj.getOwnerDocument()).createTextNode(JavaScriptEngine.toString(obj));
}
/**
* Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
* just before this ChildNode.
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
*/
protected static void before(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
for (final Object arg : args) {
final Node node = toNodeOrTextNode((Node) thisObj, arg);
((Node) thisObj).getDomNodeOrDie().insertBefore(node.getDomNodeOrDie());
}
}
/**
* Replaces this ChildNode in the children list of its parent with a set of Node or DOMString objects.
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
*/
protected static void replaceWith(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
final DomNode parentNode = thisDomNode.getParentNode();
if (args.length == 0) {
parentNode.removeChild(thisDomNode);
return;
}
final DomNode nextSibling = thisDomNode.getNextSibling();
boolean isFirst = true;
for (final Object arg : args) {
final DomNode newNode = toNodeOrTextNode((Node) thisObj, arg).getDomNodeOrDie();
if (isFirst) {
isFirst = false;
thisDomNode.replace(newNode);
}
else {
if (nextSibling == null) {
parentNode.appendChild(newNode);
}
else {
nextSibling.insertBefore(newNode);
}
}
}
}
}