View Javadoc
1   /*
2    * Copyright (c) 2002-2017 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.dom;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_ELEMENT_BASE_URL_NULL;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_NODE_INSERT_BEFORE_REF_OPTIONAL;
20  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
21  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
22  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
23  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF45;
24  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF52;
25  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
26  
27  import java.util.ArrayList;
28  import java.util.LinkedList;
29  import java.util.List;
30  
31  import com.gargoylesoftware.htmlunit.SgmlPage;
32  import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
33  import com.gargoylesoftware.htmlunit.html.DomElement;
34  import com.gargoylesoftware.htmlunit.html.DomNode;
35  import com.gargoylesoftware.htmlunit.html.HtmlInlineFrame;
36  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
37  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstant;
38  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
39  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
40  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
41  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
42  import com.gargoylesoftware.htmlunit.javascript.host.Element;
43  import com.gargoylesoftware.htmlunit.javascript.host.event.EventTarget;
44  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
45  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
46  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLHtmlElement;
47  
48  import net.sourceforge.htmlunit.corejs.javascript.Context;
49  import net.sourceforge.htmlunit.corejs.javascript.Function;
50  import net.sourceforge.htmlunit.corejs.javascript.Interpreter;
51  import net.sourceforge.htmlunit.corejs.javascript.JavaScriptException;
52  import net.sourceforge.htmlunit.corejs.javascript.RhinoException;
53  import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
54  import net.sourceforge.htmlunit.corejs.javascript.Undefined;
55  
56  /**
57   * The JavaScript object {@code Node} which is the base class for all DOM
58   * objects. This will typically wrap an instance of {@link DomNode}.
59   *
60   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
61   * @author David K. Taylor
62   * @author Barnaby Court
63   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
64   * @author <a href="mailto:george@murnock.com">George Murnock</a>
65   * @author Chris Erskine
66   * @author Bruce Faulkner
67   * @author Ahmed Ashour
68   * @author Ronald Brill
69   * @author Frank Danek
70   */
71  @JsxClass
72  public class Node extends EventTarget {
73  
74      /** @see org.w3c.dom.Node#ELEMENT_NODE */
75      @JsxConstant
76      public static final short ELEMENT_NODE = org.w3c.dom.Node.ELEMENT_NODE;
77  
78      /** @see org.w3c.dom.Node#ATTRIBUTE_NODE */
79      @JsxConstant
80      public static final short ATTRIBUTE_NODE = org.w3c.dom.Node.ATTRIBUTE_NODE;
81  
82      /** @see org.w3c.dom.Node#TEXT_NODE */
83      @JsxConstant
84      public static final short TEXT_NODE = org.w3c.dom.Node.TEXT_NODE;
85  
86      /** @see org.w3c.dom.Node#CDATA_SECTION_NODE */
87      @JsxConstant
88      public static final short CDATA_SECTION_NODE = org.w3c.dom.Node.CDATA_SECTION_NODE;
89  
90      /** @see org.w3c.dom.Node#ENTITY_REFERENCE_NODE */
91      @JsxConstant
92      public static final short ENTITY_REFERENCE_NODE = org.w3c.dom.Node.ENTITY_REFERENCE_NODE;
93  
94      /** @see org.w3c.dom.Node#ENTITY_NODE */
95      @JsxConstant
96      public static final short ENTITY_NODE = org.w3c.dom.Node.ENTITY_NODE;
97  
98      /** @see org.w3c.dom.Node#PROCESSING_INSTRUCTION_NODE */
99      @JsxConstant
100     public static final short PROCESSING_INSTRUCTION_NODE = org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE;
101 
102     /** @see org.w3c.dom.Node#COMMENT_NODE */
103     @JsxConstant
104     public static final short COMMENT_NODE = org.w3c.dom.Node.COMMENT_NODE;
105 
106     /** @see org.w3c.dom.Node#DOCUMENT_NODE */
107     @JsxConstant
108     public static final short DOCUMENT_NODE = org.w3c.dom.Node.DOCUMENT_NODE;
109 
110     /** @see org.w3c.dom.Node#DOCUMENT_TYPE_NODE */
111     @JsxConstant
112     public static final short DOCUMENT_TYPE_NODE = org.w3c.dom.Node.DOCUMENT_TYPE_NODE;
113 
114     /** @see org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE */
115     @JsxConstant
116     public static final short DOCUMENT_FRAGMENT_NODE = org.w3c.dom.Node.DOCUMENT_FRAGMENT_NODE;
117 
118     /** @see org.w3c.dom.Node#NOTATION_NODE */
119     @JsxConstant
120     public static final short NOTATION_NODE = org.w3c.dom.Node.NOTATION_NODE;
121 
122     /** @see org.w3c.dom.Node#DOCUMENT_POSITION_DISCONNECTED */
123     @JsxConstant
124     public static final short DOCUMENT_POSITION_DISCONNECTED = org.w3c.dom.Node.DOCUMENT_POSITION_DISCONNECTED;
125 
126     /** @see org.w3c.dom.Node#DOCUMENT_POSITION_PRECEDING */
127     @JsxConstant
128     public static final short DOCUMENT_POSITION_PRECEDING = org.w3c.dom.Node.DOCUMENT_POSITION_PRECEDING;
129 
130     /** @see org.w3c.dom.Node#DOCUMENT_POSITION_FOLLOWING */
131     @JsxConstant
132     public static final short DOCUMENT_POSITION_FOLLOWING = org.w3c.dom.Node.DOCUMENT_POSITION_FOLLOWING;
133 
134     /** @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINS */
135     @JsxConstant
136     public static final short DOCUMENT_POSITION_CONTAINS = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINS;
137 
138     /** @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINED_BY */
139     @JsxConstant
140     public static final short DOCUMENT_POSITION_CONTAINED_BY = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINED_BY;
141 
142     /** @see org.w3c.dom.Node#DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC */
143     @JsxConstant
144     public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
145         = org.w3c.dom.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
146 
147     /** "Live" child nodes collection; has to be a member to have equality (==) working. */
148     private NodeList childNodes_;
149 
150     /**
151      * Creates an instance.
152      */
153     @JsxConstructor({CHROME, FF, EDGE})
154     public Node() {
155         // Empty.
156     }
157 
158     /**
159      * Gets the JavaScript property {@code nodeType} for the current node.
160      * @return the node type
161      */
162     @JsxGetter
163     public short getNodeType() {
164         return getDomNodeOrDie().getNodeType();
165     }
166 
167     /**
168      * Gets the JavaScript property {@code nodeName} for the current node.
169      * @return the node name
170      */
171     @JsxGetter
172     public String getNodeName() {
173         return getDomNodeOrDie().getNodeName();
174     }
175 
176     /**
177      * Gets the JavaScript property {@code nodeValue} for the current node.
178      * @return the node value
179      */
180     @JsxGetter
181     public String getNodeValue() {
182         return getDomNodeOrDie().getNodeValue();
183     }
184 
185     /**
186      * Sets the JavaScript property {@code nodeValue} for the current node.
187      * @param newValue the new node value
188      */
189     @JsxSetter
190     public void setNodeValue(final String newValue) {
191         getDomNodeOrDie().setNodeValue(newValue);
192     }
193 
194     /**
195      * Adds a DOM node to the node.
196      * @param childObject the node to add to this node
197      * @return the newly added child node
198      */
199     @JsxFunction
200     public Object appendChild(final Object childObject) {
201         Object appendedChild = null;
202         if (childObject instanceof Node) {
203             final Node childNode = (Node) childObject;
204 
205             // is the node allowed here?
206             if (!isNodeInsertable(childNode)) {
207                 throw asJavaScriptException(
208                     new DOMException("Node cannot be inserted at the specified point in the hierarchy",
209                         DOMException.HIERARCHY_REQUEST_ERR));
210             }
211 
212             // Get XML node for the DOM node passed in
213             final DomNode childDomNode = childNode.getDomNodeOrDie();
214 
215             // Get the parent XML node that the child should be added to.
216             final DomNode parentNode = getDomNodeOrDie();
217 
218             // Append the child to the parent node
219             parentNode.appendChild(childDomNode);
220             appendedChild = childObject;
221 
222             initInlineFrameIfNeeded(childDomNode);
223             for (final DomNode domNode : childDomNode.getDescendants()) {
224                 initInlineFrameIfNeeded(domNode);
225             }
226         }
227         return appendedChild;
228     }
229 
230     /**
231      * If we have added a new iframe that
232      * had no source attribute, we have to take care the
233      * 'onload' handler is triggered.
234      *
235      * @param childDomNode
236      */
237     private static void initInlineFrameIfNeeded(final DomNode childDomNode) {
238         if (childDomNode instanceof HtmlInlineFrame) {
239             final HtmlInlineFrame frame = (HtmlInlineFrame) childDomNode;
240             if (DomElement.ATTRIBUTE_NOT_DEFINED == frame.getSrcAttribute()) {
241                 frame.loadInnerPage();
242             }
243         }
244     }
245 
246     /**
247      * Encapsulates the given {@link DOMException} into a Rhino-compatible exception.
248      *
249      * @param exception the exception to encapsulate
250      * @return the created exception
251      */
252     protected RhinoException asJavaScriptException(final DOMException exception) {
253         exception.setPrototype(getWindow().getPrototype(exception.getClass()));
254         exception.setParentScope(getWindow());
255 
256         // get current line and file name
257         // this method can only be used in interpreted mode. If one day we choose to use compiled mode,
258         // then we'll have to find an other way here.
259         final String fileName;
260         final int lineNumber;
261         if (Context.getCurrentContext().getOptimizationLevel() == -1) {
262             final int[] linep = new int[1];
263             final String sourceName = new Interpreter().getSourcePositionFromStack(Context.getCurrentContext(), linep);
264             fileName = sourceName.replaceFirst("script in (.*) from .*", "$1");
265             lineNumber = linep[0];
266         }
267         else {
268             throw new Error("HtmlUnit not ready to run in compiled mode");
269         }
270 
271         exception.setLocation(fileName, lineNumber);
272 
273         return new JavaScriptException(exception, fileName, lineNumber);
274     }
275 
276     /**
277      * Add a DOM node as a child to this node before the referenced node.
278      * If the referenced node is null, append to the end.
279      * @param context the JavaScript context
280      * @param thisObj the scriptable
281      * @param args the arguments passed into the method
282      * @param function the function
283      * @return the newly added child node
284      */
285     @JsxFunction
286     public static Object insertBefore(
287             final Context context, final Scriptable thisObj, final Object[] args, final Function function) {
288         return ((Node) thisObj).insertBeforeImpl(args);
289     }
290 
291     /**
292      * Add a DOM node as a child to this node before the referenced node.
293      * If the referenced node is null, append to the end.
294      * @param args the arguments
295      * @return the newly added child node
296      */
297     protected Object insertBeforeImpl(final Object[] args) {
298         final Object newChildObject = args[0];
299         final Object refChildObject;
300         if (args.length > 1) {
301             refChildObject = args[1];
302         }
303         else {
304             refChildObject = Undefined.instance;
305         }
306         Object insertedChild = null;
307 
308         if (newChildObject instanceof Node) {
309             final Node newChild = (Node) newChildObject;
310             final DomNode newChildNode = newChild.getDomNodeOrDie();
311 
312             // is the node allowed here?
313             if (!isNodeInsertable(newChild)) {
314                 throw asJavaScriptException(
315                     new DOMException("Node cannot be inserted at the specified point in the hierarchy",
316                         DOMException.HIERARCHY_REQUEST_ERR));
317             }
318             if (newChildNode instanceof DomDocumentFragment) {
319                 final DomDocumentFragment fragment = (DomDocumentFragment) newChildNode;
320                 for (final DomNode child : fragment.getChildren()) {
321                     if (!isNodeInsertable((Node) child.getScriptableObject())) {
322                         throw asJavaScriptException(
323                             new DOMException("Node cannot be inserted at the specified point in the hierarchy",
324                                 DOMException.HIERARCHY_REQUEST_ERR));
325                     }
326                 }
327             }
328 
329             // extract refChild
330             final DomNode refChildNode;
331             if (refChildObject == Undefined.instance) {
332                 if (args.length == 2 || getBrowserVersion().hasFeature(JS_NODE_INSERT_BEFORE_REF_OPTIONAL)) {
333                     refChildNode = null;
334                 }
335                 else {
336                     throw Context.reportRuntimeError("insertBefore: not enough arguments");
337                 }
338             }
339             else if (refChildObject != null) {
340                 refChildNode = ((Node) refChildObject).getDomNodeOrDie();
341             }
342             else {
343                 refChildNode = null;
344             }
345 
346             final DomNode domNode = getDomNodeOrDie();
347 
348             try {
349                 domNode.insertBefore(newChildNode, refChildNode);
350             }
351             catch (final org.w3c.dom.DOMException e) {
352                 throw asJavaScriptException(
353                         new DOMException(e.getMessage(),
354                             DOMException.HIERARCHY_REQUEST_ERR));
355             }
356             insertedChild = newChild;
357         }
358         return insertedChild;
359     }
360 
361     /**
362      * Indicates if the node can be inserted.
363      * @param childObject the node
364      * @return {@code false} if it is not allowed here
365      */
366     private static boolean isNodeInsertable(final Node childObject) {
367         if (childObject instanceof HTMLHtmlElement) {
368             final DomNode domNode = childObject.getDomNodeOrDie();
369             return !(domNode.getPage().getDocumentElement() == domNode);
370         }
371         return true;
372     }
373 
374     /**
375      * Removes the DOM node from its parent.
376      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove">MDN documentation</a>
377      */
378     protected void remove() {
379         getDomNodeOrDie().remove();
380     }
381 
382     /**
383      * Removes a DOM node from this node.
384      * @param childObject the node to remove from this node
385      * @return the removed child node
386      */
387     @JsxFunction
388     public Object removeChild(final Object childObject) {
389         if (!(childObject instanceof Node)) {
390             return null;
391         }
392 
393         // Get XML node for the DOM node passed in
394         final DomNode childNode = ((Node) childObject).getDomNodeOrDie();
395 
396         if (!getDomNodeOrDie().isAncestorOf(childNode)) {
397             Context.throwAsScriptRuntimeEx(new Exception("NotFoundError: Failed to execute 'removeChild' on '"
398                         + this + "': The node to be removed is not a child of this node."));
399         }
400         // Remove the child from the parent node
401         childNode.remove();
402         return childObject;
403     }
404 
405     /**
406      * Replaces a child DOM node with another DOM node.
407      * @param newChildObject the node to add as a child of this node
408      * @param oldChildObject the node to remove as a child of this node
409      * @return the removed child node
410      */
411     @JsxFunction
412     public Object replaceChild(final Object newChildObject, final Object oldChildObject) {
413         Object removedChild = null;
414 
415         if (newChildObject instanceof DocumentFragment) {
416             final DocumentFragment fragment = (DocumentFragment) newChildObject;
417             Node firstNode = null;
418             final Node refChildObject = ((Node) oldChildObject).getNextSibling();
419             for (final DomNode node : fragment.getDomNodeOrDie().getChildren()) {
420                 if (firstNode == null) {
421                     replaceChild(node.getScriptableObject(), oldChildObject);
422                     firstNode = (Node) node.getScriptableObject();
423                 }
424                 else {
425                     insertBeforeImpl(new Object[] {node.getScriptableObject(), refChildObject});
426                 }
427             }
428             if (firstNode == null) {
429                 removeChild(oldChildObject);
430             }
431             removedChild = oldChildObject;
432         }
433         else if (newChildObject instanceof Node && oldChildObject instanceof Node) {
434             final Node newChild = (Node) newChildObject;
435 
436             // is the node allowed here?
437             if (!isNodeInsertable(newChild)) {
438                 throw Context.reportRuntimeError("Node cannot be inserted at the specified point in the hierarchy");
439             }
440 
441             // Get XML nodes for the DOM nodes passed in
442             final DomNode newChildNode = newChild.getDomNodeOrDie();
443             final DomNode oldChildNode = ((Node) oldChildObject).getDomNodeOrDie();
444 
445             // Replace the old child with the new child.
446             oldChildNode.replace(newChildNode);
447             removedChild = oldChildObject;
448         }
449 
450         return removedChild;
451     }
452 
453     /**
454      * Clones this node.
455      * @param deep if {@code true}, recursively clones all descendants
456      * @return the newly cloned node
457      */
458     @JsxFunction
459     public Object cloneNode(final boolean deep) {
460         final DomNode domNode = getDomNodeOrDie();
461         final DomNode clonedNode = domNode.cloneNode(deep);
462 
463         final Node jsClonedNode = getJavaScriptNode(clonedNode);
464         return jsClonedNode;
465     }
466 
467     /**
468      * This method provides a way to determine whether two Node references returned by
469      * the implementation reference the same object.
470      * When two Node references are references to the same object, even if through a proxy,
471      * the references may be used completely interchangeably, such that all attributes
472      * have the same values and calling the same DOM method on either reference always has exactly the same effect.
473      *
474      * @param other the node to test against
475      *
476      * @return whether this node is the same node as the given one
477      */
478     @JsxFunction({CHROME, FF52, IE})
479     public boolean isSameNode(final Object other) {
480         return other == this;
481     }
482 
483     /**
484      * Returns whether this node has any children.
485      * @return boolean true if this node has any children, false otherwise
486      */
487     @JsxFunction
488     public boolean hasChildNodes() {
489         return getDomNodeOrDie().getChildren().iterator().hasNext();
490     }
491 
492     /**
493      * Returns the child nodes of the current element.
494      * @return the child nodes of the current element
495      */
496     @JsxGetter
497     public NodeList getChildNodes() {
498         if (childNodes_ == null) {
499             final DomNode node = getDomNodeOrDie();
500             childNodes_ = new NodeList(node, false) {
501                 @Override
502                 protected List<DomNode> computeElements() {
503                     final List<DomNode> response = new ArrayList<>();
504                     for (final DomNode child : node.getChildren()) {
505                         response.add(child);
506                     }
507 
508                     return response;
509                 }
510             };
511         }
512         return childNodes_;
513     }
514 
515     /**
516      * Returns this node's parent node.
517      * @return this node's parent node
518      */
519     public final Node getParent() {
520         return getJavaScriptNode(getDomNodeOrDie().getParentNode());
521     }
522 
523     /**
524      * Gets the JavaScript property {@code parentNode} for the node that
525      * contains the current node.
526      * @return the parent node
527      */
528     @JsxGetter
529     public Object getParentNode() {
530         return getJavaScriptNode(getDomNodeOrDie().getParentNode());
531     }
532 
533     /**
534      * Gets the JavaScript property {@code nextSibling} for the node that
535      * contains the current node.
536      * @return the next sibling node or null if the current node has
537      * no next sibling.
538      */
539     @JsxGetter
540     public Node getNextSibling() {
541         return getJavaScriptNode(getDomNodeOrDie().getNextSibling());
542     }
543 
544     /**
545      * Gets the JavaScript property {@code previousSibling} for the node that
546      * contains the current node.
547      * @return the previous sibling node or null if the current node has
548      * no previous sibling.
549      */
550     @JsxGetter
551     public Node getPreviousSibling() {
552         return getJavaScriptNode(getDomNodeOrDie().getPreviousSibling());
553     }
554 
555     /**
556      * Gets the JavaScript property {@code firstChild} for the node that
557      * contains the current node.
558      * @return the first child node or null if the current node has
559      * no children.
560      */
561     @JsxGetter
562     public Node getFirstChild() {
563         return getJavaScriptNode(getDomNodeOrDie().getFirstChild());
564     }
565 
566     /**
567      * Gets the JavaScript property {@code lastChild} for the node that
568      * contains the current node.
569      * @return the last child node or null if the current node has
570      * no children.
571      */
572     @JsxGetter
573     public Node getLastChild() {
574         return getJavaScriptNode(getDomNodeOrDie().getLastChild());
575     }
576 
577     /**
578      * Gets the JavaScript node for a given DomNode.
579      * @param domNode the DomNode
580      * @return the JavaScript node or null if the DomNode was null
581      */
582     protected Node getJavaScriptNode(final DomNode domNode) {
583         if (domNode == null) {
584             return null;
585         }
586         return (Node) getScriptableFor(domNode);
587     }
588 
589     /**
590      * Returns the owner document.
591      * @return the document
592      */
593     @JsxGetter
594     public Object getOwnerDocument() {
595         final Object document = getDomNodeOrDie().getOwnerDocument();
596         if (document != null) {
597             return ((SgmlPage) document).getScriptableObject();
598         }
599         return null;
600     }
601 
602     /**
603      * Compares the positions of this node and the provided node within the document.
604      * @param node node object that specifies the node to check
605      * @return how the node is positioned relatively to the reference node.
606      * @see <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM level 3</a>
607      * @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
608      */
609     @JsxFunction
610     public short compareDocumentPosition(final Object node) {
611         if (!(node instanceof Node)) {
612             throw Context.reportRuntimeError("Could not convert JavaScript argument arg 0");
613         }
614         return getDomNodeOrDie().compareDocumentPosition(((Node) node).getDomNodeOrDie());
615     }
616 
617     /**
618      * Merges adjacent TextNode objects to produce a normalized document object model.
619      */
620     @JsxFunction
621     public void normalize() {
622         getDomNodeOrDie().normalize();
623     }
624 
625     /**
626      * Gets the textContent attribute.
627      * @return the contents of this node as text
628      */
629     @JsxGetter
630     public String getTextContent() {
631         return getDomNodeOrDie().getTextContent();
632     }
633 
634     /**
635      * Replace all children elements of this element with the supplied value.
636      * @param value - the new value for the contents of this node
637      */
638     @JsxSetter
639     public void setTextContent(final Object value) {
640         getDomNodeOrDie().setTextContent(value == null ? null : Context.toString(value));
641     }
642 
643     /**
644      * Gets the JavaScript property {@code parentElement}.
645      * @return the parent element
646      * @see #getParentNode()
647      */
648     @JsxGetter({CHROME, FF})
649     public Element getParentElement() {
650         final Node parent = getParent();
651         if (!(parent instanceof Element)) {
652             return null;
653         }
654         return (Element) parent;
655     }
656 
657     /**
658      * Returns the attributes of this XML element.
659      * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/Node.attributes">Gecko DOM Reference</a>
660      * @return the attributes of this XML element
661      */
662     @JsxGetter(IE)
663     public Object getAttributes() {
664         return null;
665     }
666 
667     /**
668      * Checks whether the given element is contained within this object.
669      * @param element element object that specifies the element to check
670      * @return true if the element is contained within this object
671      */
672     @JsxFunction({CHROME, FF})
673     public boolean contains(final Object element) {
674         if (!(element instanceof Node)) {
675             if (getBrowserVersion().hasFeature(JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG)) {
676                 return false;
677             }
678             throw Context.reportRuntimeError("Could not convert JavaScript argument arg 0");
679         }
680 
681         if (getBrowserVersion().hasFeature(JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG)) {
682             if (element instanceof CharacterData) {
683                 return false;
684             }
685             if (this instanceof CharacterData) {
686                 throw Context.reportRuntimeError("Function 'contains' not available for text nodes.");
687             }
688         }
689 
690         for (Node parent = (Node) element; parent != null; parent = parent.getParentElement()) {
691             if (this == parent) {
692                 return true;
693             }
694         }
695         return false;
696     }
697 
698     /**
699      * Returns the Base URI as a string.
700      * @return the Base URI as a string
701      */
702     @JsxGetter({CHROME, FF})
703     public String getBaseURI() {
704         if ("Element".equals(getClass().getSimpleName()) && getBrowserVersion().hasFeature(JS_ELEMENT_BASE_URL_NULL)) {
705             return null;
706         }
707         return getDomNodeOrDie().getBaseURI();
708     }
709 
710     /**
711      * Returns true when the current element has any attributes or not.
712      * @return true if an attribute is specified on this element
713      */
714     @JsxFunction(IE)
715     public boolean hasAttributes() {
716         return getDomNodeOrDie().hasAttributes();
717     }
718 
719     /**
720      * Returns the namespace prefix.
721      * @return the namespace prefix
722      */
723     @JsxGetter({FF45, IE})
724     public Object getPrefix() {
725         return getDomNodeOrDie().getPrefix();
726     }
727 
728     /**
729      * Returns the local name of this attribute.
730      * @return the local name of this attribute
731      */
732     @JsxGetter({FF45, IE})
733     public Object getLocalName() {
734         return getDomNodeOrDie().getLocalName();
735     }
736 
737     /**
738      * Returns the URI that identifies an XML namespace.
739      * @return the URI that identifies an XML namespace
740      */
741     @JsxGetter({FF45, IE})
742     public Object getNamespaceURI() {
743         return getDomNodeOrDie().getNamespaceURI();
744     }
745 
746     /**
747      * Returns the current number of child elements.
748      * @return the child element count
749      */
750     protected int getChildElementCount() {
751         return ((DomElement) getDomNodeOrDie()).getChildElementCount();
752     }
753 
754     /**
755      * Returns the first element child.
756      * @return the first element child
757      */
758     protected Element getFirstElementChild() {
759         final DomElement child = ((DomElement) getDomNodeOrDie()).getFirstElementChild();
760         if (child != null) {
761             return (Element) child.getScriptableObject();
762         }
763         return null;
764     }
765 
766     /**
767      * Returns the last element child.
768      * @return the last element child
769      */
770     protected Element getLastElementChild() {
771         final DomElement child = ((DomElement) getDomNodeOrDie()).getLastElementChild();
772         if (child != null) {
773             return (Element) child.getScriptableObject();
774         }
775         return null;
776     }
777 
778     /**
779      * Gets the children of the current node.
780      * @see <a href="http://msdn.microsoft.com/en-us/library/ms537446.aspx">MSDN documentation</a>
781      * @return the child at the given position
782      */
783     protected HTMLCollection getChildren() {
784         final DomNode node = getDomNodeOrDie();
785         final HTMLCollection collection = new HTMLCollection(node, false) {
786             @Override
787             protected List<DomNode> computeElements() {
788                 final List<DomNode> children = new LinkedList<>();
789                 for (DomNode domNode : node.getChildNodes()) {
790                     if (domNode instanceof DomElement) {
791                         children.add(domNode);
792                     }
793                 }
794                 return children;
795             }
796         };
797         return collection;
798     }
799 
800     /**
801      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
802      * just after this ChildNode.
803      * @param context the context
804      * @param thisObj this object
805      * @param args the arguments
806      * @param function the function
807      */
808     protected static void after(final Context context, final Scriptable thisObj, final Object[] args,
809             final Function function) {
810         final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
811         final DomNode parentNode = thisDomNode.getParentNode();
812         final DomNode nextSibling = thisDomNode.getNextSibling();
813         for (Object arg : args) {
814             final Node node = toNodeOrTextNode((Node) thisObj, arg);
815             final DomNode newNode = node.getDomNodeOrDie();
816             if (nextSibling != null) {
817                 nextSibling.insertBefore(newNode);
818             }
819             else {
820                 parentNode.appendChild(newNode);
821             }
822         }
823     }
824 
825     private static Node toNodeOrTextNode(final Node thisObj, final Object obj) {
826         if (obj instanceof Node) {
827             return (Node) obj;
828         }
829         return (Node)
830                 ((HTMLDocument) thisObj.getOwnerDocument()).createTextNode(Context.toString(obj));
831     }
832 
833     /**
834      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
835      * just before this ChildNode.
836      * @param context the context
837      * @param thisObj this object
838      * @param args the arguments
839      * @param function the function
840      */
841     protected static void before(final Context context, final Scriptable thisObj, final Object[] args,
842             final Function function) {
843         for (Object arg : args) {
844             final Node node = toNodeOrTextNode((Node) thisObj, arg);
845             ((Node) thisObj).getDomNodeOrDie().insertBefore(node.getDomNodeOrDie());
846         }
847     }
848 
849     /**
850      * Replaces this ChildNode in the children list of its parent with a set of Node or DOMString objects.
851      * @param context the context
852      * @param thisObj this object
853      * @param args the arguments
854      * @param function the function
855      */
856     protected static void replaceWith(final Context context, final Scriptable thisObj, final Object[] args,
857             final Function function) {
858         final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
859         final DomNode parentNode = thisDomNode.getParentNode();
860         final DomNode nextSibling = thisDomNode.getNextSibling();
861         boolean isFirst = true;
862         for (Object arg : args) {
863             final DomNode newNode = toNodeOrTextNode((Node) thisObj, arg).getDomNodeOrDie();
864             if (isFirst) {
865                 isFirst = false;
866                 thisDomNode.replace(newNode);
867             }
868             else {
869                 if (nextSibling != null) {
870                     nextSibling.insertBefore(newNode);
871                 }
872                 else {
873                     parentNode.appendChild(newNode);
874                 }
875             }
876         }
877     }
878 }