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_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_NODE_INSERT_BEFORE_REF_OPTIONAL;
19  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
20  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
21  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
22  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF45;
23  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF52;
24  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
25  
26  import java.util.ArrayList;
27  import java.util.LinkedList;
28  import java.util.List;
29  
30  import com.gargoylesoftware.htmlunit.SgmlPage;
31  import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
32  import com.gargoylesoftware.htmlunit.html.DomElement;
33  import com.gargoylesoftware.htmlunit.html.DomNode;
34  import com.gargoylesoftware.htmlunit.html.HtmlInlineFrame;
35  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
36  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstant;
37  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
38  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
39  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
40  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
41  import com.gargoylesoftware.htmlunit.javascript.host.Element;
42  import com.gargoylesoftware.htmlunit.javascript.host.event.EventTarget;
43  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
44  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
45  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLHtmlElement;
46  
47  import net.sourceforge.htmlunit.corejs.javascript.Context;
48  import net.sourceforge.htmlunit.corejs.javascript.Function;
49  import net.sourceforge.htmlunit.corejs.javascript.Interpreter;
50  import net.sourceforge.htmlunit.corejs.javascript.JavaScriptException;
51  import net.sourceforge.htmlunit.corejs.javascript.RhinoException;
52  import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
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         if (args.length < 1) {
299             throw ScriptRuntime.typeError(
300                     "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 0 present.");
301         }
302 
303         final Object newChildObject = args[0];
304         final Object refChildObject;
305         if (args.length > 1) {
306             refChildObject = args[1];
307         }
308         else {
309             refChildObject = Undefined.instance;
310         }
311         Object insertedChild = null;
312 
313         if (newChildObject instanceof Node) {
314             final Node newChild = (Node) newChildObject;
315             final DomNode newChildNode = newChild.getDomNodeOrDie();
316 
317             // is the node allowed here?
318             if (!isNodeInsertable(newChild)) {
319                 throw ScriptRuntime.constructError("ReferenceError",
320                         "Node cannot be inserted at the specified point in the hierarchy");
321             }
322             if (newChildNode instanceof DomDocumentFragment) {
323                 final DomDocumentFragment fragment = (DomDocumentFragment) newChildNode;
324                 for (final DomNode child : fragment.getChildren()) {
325                     if (!isNodeInsertable((Node) child.getScriptableObject())) {
326                         throw ScriptRuntime.constructError("ReferenceError",
327                                 "Node cannot be inserted at the specified point in the hierarchy");
328                     }
329                 }
330             }
331 
332             // extract refChild
333             final DomNode refChildNode;
334             if (refChildObject == Undefined.instance) {
335                 if (args.length == 2 || getBrowserVersion().hasFeature(JS_NODE_INSERT_BEFORE_REF_OPTIONAL)) {
336                     refChildNode = null;
337                 }
338                 else {
339                     throw ScriptRuntime.typeError(
340                             "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present.");
341                 }
342             }
343             else if (refChildObject != null) {
344                 refChildNode = ((Node) refChildObject).getDomNodeOrDie();
345             }
346             else {
347                 refChildNode = null;
348             }
349 
350             final DomNode domNode = getDomNodeOrDie();
351 
352             try {
353                 domNode.insertBefore(newChildNode, refChildNode);
354             }
355             catch (final org.w3c.dom.DOMException e) {
356                 throw ScriptRuntime.constructError("ReferenceError", e.getMessage());
357             }
358             insertedChild = newChild;
359         }
360         return insertedChild;
361     }
362 
363     /**
364      * Indicates if the node can be inserted.
365      * @param childObject the node
366      * @return {@code false} if it is not allowed here
367      */
368     private static boolean isNodeInsertable(final Node childObject) {
369         if (childObject instanceof HTMLHtmlElement) {
370             final DomNode domNode = childObject.getDomNodeOrDie();
371             return !(domNode.getPage().getDocumentElement() == domNode);
372         }
373         return true;
374     }
375 
376     /**
377      * Removes the DOM node from its parent.
378      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove">MDN documentation</a>
379      */
380     protected void remove() {
381         getDomNodeOrDie().remove();
382     }
383 
384     /**
385      * Removes a DOM node from this node.
386      * @param childObject the node to remove from this node
387      * @return the removed child node
388      */
389     @JsxFunction
390     public Object removeChild(final Object childObject) {
391         if (!(childObject instanceof Node)) {
392             return null;
393         }
394 
395         // Get XML node for the DOM node passed in
396         final DomNode childNode = ((Node) childObject).getDomNodeOrDie();
397 
398         if (!getDomNodeOrDie().isAncestorOf(childNode)) {
399             Context.throwAsScriptRuntimeEx(new Exception("NotFoundError: Failed to execute 'removeChild' on '"
400                         + this + "': The node to be removed is not a child of this node."));
401         }
402         // Remove the child from the parent node
403         childNode.remove();
404         return childObject;
405     }
406 
407     /**
408      * Replaces a child DOM node with another DOM node.
409      * @param newChildObject the node to add as a child of this node
410      * @param oldChildObject the node to remove as a child of this node
411      * @return the removed child node
412      */
413     @JsxFunction
414     public Object replaceChild(final Object newChildObject, final Object oldChildObject) {
415         Object removedChild = null;
416 
417         if (newChildObject instanceof DocumentFragment) {
418             final DocumentFragment fragment = (DocumentFragment) newChildObject;
419             Node firstNode = null;
420             final Node refChildObject = ((Node) oldChildObject).getNextSibling();
421             for (final DomNode node : fragment.getDomNodeOrDie().getChildren()) {
422                 if (firstNode == null) {
423                     replaceChild(node.getScriptableObject(), oldChildObject);
424                     firstNode = (Node) node.getScriptableObject();
425                 }
426                 else {
427                     insertBeforeImpl(new Object[] {node.getScriptableObject(), refChildObject});
428                 }
429             }
430             if (firstNode == null) {
431                 removeChild(oldChildObject);
432             }
433             removedChild = oldChildObject;
434         }
435         else if (newChildObject instanceof Node && oldChildObject instanceof Node) {
436             final Node newChild = (Node) newChildObject;
437 
438             // is the node allowed here?
439             if (!isNodeInsertable(newChild)) {
440                 throw Context.reportRuntimeError("Node cannot be inserted at the specified point in the hierarchy");
441             }
442 
443             // Get XML nodes for the DOM nodes passed in
444             final DomNode newChildNode = newChild.getDomNodeOrDie();
445             final DomNode oldChildNode = ((Node) oldChildObject).getDomNodeOrDie();
446 
447             // Replace the old child with the new child.
448             oldChildNode.replace(newChildNode);
449             removedChild = oldChildObject;
450         }
451 
452         return removedChild;
453     }
454 
455     /**
456      * Clones this node.
457      * @param deep if {@code true}, recursively clones all descendants
458      * @return the newly cloned node
459      */
460     @JsxFunction
461     public Object cloneNode(final boolean deep) {
462         final DomNode domNode = getDomNodeOrDie();
463         final DomNode clonedNode = domNode.cloneNode(deep);
464 
465         final Node jsClonedNode = getJavaScriptNode(clonedNode);
466         return jsClonedNode;
467     }
468 
469     /**
470      * This method provides a way to determine whether two Node references returned by
471      * the implementation reference the same object.
472      * When two Node references are references to the same object, even if through a proxy,
473      * the references may be used completely interchangeably, such that all attributes
474      * have the same values and calling the same DOM method on either reference always has exactly the same effect.
475      *
476      * @param other the node to test against
477      *
478      * @return whether this node is the same node as the given one
479      */
480     @JsxFunction({CHROME, FF52, IE})
481     public boolean isSameNode(final Object other) {
482         return other == this;
483     }
484 
485     /**
486      * Returns whether this node has any children.
487      * @return boolean true if this node has any children, false otherwise
488      */
489     @JsxFunction
490     public boolean hasChildNodes() {
491         return getDomNodeOrDie().getChildren().iterator().hasNext();
492     }
493 
494     /**
495      * Returns the child nodes of the current element.
496      * @return the child nodes of the current element
497      */
498     @JsxGetter
499     public NodeList getChildNodes() {
500         if (childNodes_ == null) {
501             final DomNode node = getDomNodeOrDie();
502             childNodes_ = new NodeList(node, false) {
503                 @Override
504                 protected List<DomNode> computeElements() {
505                     final List<DomNode> response = new ArrayList<>();
506                     for (final DomNode child : node.getChildren()) {
507                         response.add(child);
508                     }
509 
510                     return response;
511                 }
512             };
513         }
514         return childNodes_;
515     }
516 
517     /**
518      * Returns this node's parent node.
519      * @return this node's parent node
520      */
521     public final Node getParent() {
522         return getJavaScriptNode(getDomNodeOrDie().getParentNode());
523     }
524 
525     /**
526      * Gets the JavaScript property {@code parentNode} for the node that
527      * contains the current node.
528      * @return the parent node
529      */
530     @JsxGetter
531     public Object getParentNode() {
532         return getJavaScriptNode(getDomNodeOrDie().getParentNode());
533     }
534 
535     /**
536      * Gets the JavaScript property {@code nextSibling} for the node that
537      * contains the current node.
538      * @return the next sibling node or null if the current node has
539      * no next sibling.
540      */
541     @JsxGetter
542     public Node getNextSibling() {
543         return getJavaScriptNode(getDomNodeOrDie().getNextSibling());
544     }
545 
546     /**
547      * Gets the JavaScript property {@code previousSibling} for the node that
548      * contains the current node.
549      * @return the previous sibling node or null if the current node has
550      * no previous sibling.
551      */
552     @JsxGetter
553     public Node getPreviousSibling() {
554         return getJavaScriptNode(getDomNodeOrDie().getPreviousSibling());
555     }
556 
557     /**
558      * Gets the JavaScript property {@code firstChild} for the node that
559      * contains the current node.
560      * @return the first child node or null if the current node has
561      * no children.
562      */
563     @JsxGetter
564     public Node getFirstChild() {
565         return getJavaScriptNode(getDomNodeOrDie().getFirstChild());
566     }
567 
568     /**
569      * Gets the JavaScript property {@code lastChild} for the node that
570      * contains the current node.
571      * @return the last child node or null if the current node has
572      * no children.
573      */
574     @JsxGetter
575     public Node getLastChild() {
576         return getJavaScriptNode(getDomNodeOrDie().getLastChild());
577     }
578 
579     /**
580      * Gets the JavaScript node for a given DomNode.
581      * @param domNode the DomNode
582      * @return the JavaScript node or null if the DomNode was null
583      */
584     protected Node getJavaScriptNode(final DomNode domNode) {
585         if (domNode == null) {
586             return null;
587         }
588         return (Node) getScriptableFor(domNode);
589     }
590 
591     /**
592      * Returns the owner document.
593      * @return the document
594      */
595     @JsxGetter
596     public Object getOwnerDocument() {
597         final Object document = getDomNodeOrDie().getOwnerDocument();
598         if (document != null) {
599             return ((SgmlPage) document).getScriptableObject();
600         }
601         return null;
602     }
603 
604     /**
605      * Compares the positions of this node and the provided node within the document.
606      * @param node node object that specifies the node to check
607      * @return how the node is positioned relatively to the reference node.
608      * @see <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM level 3</a>
609      * @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
610      */
611     @JsxFunction
612     public short compareDocumentPosition(final Object node) {
613         if (!(node instanceof Node)) {
614             throw Context.reportRuntimeError("Could not convert JavaScript argument arg 0");
615         }
616         return getDomNodeOrDie().compareDocumentPosition(((Node) node).getDomNodeOrDie());
617     }
618 
619     /**
620      * Merges adjacent TextNode objects to produce a normalized document object model.
621      */
622     @JsxFunction
623     public void normalize() {
624         getDomNodeOrDie().normalize();
625     }
626 
627     /**
628      * Gets the textContent attribute.
629      * @return the contents of this node as text
630      */
631     @JsxGetter
632     public String getTextContent() {
633         return getDomNodeOrDie().getTextContent();
634     }
635 
636     /**
637      * Replace all children elements of this element with the supplied value.
638      * @param value - the new value for the contents of this node
639      */
640     @JsxSetter
641     public void setTextContent(final Object value) {
642         getDomNodeOrDie().setTextContent(value == null ? null : Context.toString(value));
643     }
644 
645     /**
646      * Gets the JavaScript property {@code parentElement}.
647      * @return the parent element
648      * @see #getParentNode()
649      */
650     @JsxGetter({CHROME, FF})
651     public Element getParentElement() {
652         final Node parent = getParent();
653         if (!(parent instanceof Element)) {
654             return null;
655         }
656         return (Element) parent;
657     }
658 
659     /**
660      * Returns the attributes of this XML element.
661      * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/Node.attributes">Gecko DOM Reference</a>
662      * @return the attributes of this XML element
663      */
664     @JsxGetter(IE)
665     public Object getAttributes() {
666         return null;
667     }
668 
669     /**
670      * Checks whether the given element is contained within this object.
671      * @param element element object that specifies the element to check
672      * @return true if the element is contained within this object
673      */
674     @JsxFunction({CHROME, FF})
675     public boolean contains(final Object element) {
676         if (!(element instanceof Node)) {
677             if (getBrowserVersion().hasFeature(JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG)) {
678                 return false;
679             }
680             throw Context.reportRuntimeError("Could not convert JavaScript argument arg 0");
681         }
682 
683         if (getBrowserVersion().hasFeature(JS_NODE_CONTAINS_RETURNS_FALSE_FOR_INVALID_ARG)) {
684             if (element instanceof CharacterData) {
685                 return false;
686             }
687             if (this instanceof CharacterData) {
688                 throw Context.reportRuntimeError("Function 'contains' not available for text nodes.");
689             }
690         }
691 
692         for (Node parent = (Node) element; parent != null; parent = parent.getParentElement()) {
693             if (this == parent) {
694                 return true;
695             }
696         }
697         return false;
698     }
699 
700     /**
701      * Returns the Base URI as a string.
702      * @return the Base URI as a string
703      */
704     @JsxGetter({CHROME, FF})
705     public String getBaseURI() {
706         return getDomNodeOrDie().getBaseURI();
707     }
708 
709     /**
710      * Returns true when the current element has any attributes or not.
711      * @return true if an attribute is specified on this element
712      */
713     @JsxFunction(IE)
714     public boolean hasAttributes() {
715         return getDomNodeOrDie().hasAttributes();
716     }
717 
718     /**
719      * Returns the namespace prefix.
720      * @return the namespace prefix
721      */
722     @JsxGetter({FF45, IE})
723     public Object getPrefix() {
724         return getDomNodeOrDie().getPrefix();
725     }
726 
727     /**
728      * Returns the local name of this attribute.
729      * @return the local name of this attribute
730      */
731     @JsxGetter({FF45, IE})
732     public Object getLocalName() {
733         return getDomNodeOrDie().getLocalName();
734     }
735 
736     /**
737      * Returns the URI that identifies an XML namespace.
738      * @return the URI that identifies an XML namespace
739      */
740     @JsxGetter({FF45, IE})
741     public Object getNamespaceURI() {
742         return getDomNodeOrDie().getNamespaceURI();
743     }
744 
745     /**
746      * Returns the current number of child elements.
747      * @return the child element count
748      */
749     protected int getChildElementCount() {
750         return ((DomElement) getDomNodeOrDie()).getChildElementCount();
751     }
752 
753     /**
754      * Returns the first element child.
755      * @return the first element child
756      */
757     protected Element getFirstElementChild() {
758         final DomElement child = ((DomElement) getDomNodeOrDie()).getFirstElementChild();
759         if (child != null) {
760             return (Element) child.getScriptableObject();
761         }
762         return null;
763     }
764 
765     /**
766      * Returns the last element child.
767      * @return the last element child
768      */
769     protected Element getLastElementChild() {
770         final DomElement child = ((DomElement) getDomNodeOrDie()).getLastElementChild();
771         if (child != null) {
772             return (Element) child.getScriptableObject();
773         }
774         return null;
775     }
776 
777     /**
778      * Gets the children of the current node.
779      * @see <a href="http://msdn.microsoft.com/en-us/library/ms537446.aspx">MSDN documentation</a>
780      * @return the child at the given position
781      */
782     protected HTMLCollection getChildren() {
783         final DomNode node = getDomNodeOrDie();
784         final HTMLCollection collection = new HTMLCollection(node, false) {
785             @Override
786             protected List<DomNode> computeElements() {
787                 final List<DomNode> children = new LinkedList<>();
788                 for (DomNode domNode : node.getChildNodes()) {
789                     if (domNode instanceof DomElement) {
790                         children.add(domNode);
791                     }
792                 }
793                 return children;
794             }
795         };
796         return collection;
797     }
798 
799     /**
800      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
801      * just after this ChildNode.
802      * @param context the context
803      * @param thisObj this object
804      * @param args the arguments
805      * @param function the function
806      */
807     protected static void after(final Context context, final Scriptable thisObj, final Object[] args,
808             final Function function) {
809         final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
810         final DomNode parentNode = thisDomNode.getParentNode();
811         final DomNode nextSibling = thisDomNode.getNextSibling();
812         for (Object arg : args) {
813             final Node node = toNodeOrTextNode((Node) thisObj, arg);
814             final DomNode newNode = node.getDomNodeOrDie();
815             if (nextSibling != null) {
816                 nextSibling.insertBefore(newNode);
817             }
818             else {
819                 parentNode.appendChild(newNode);
820             }
821         }
822     }
823 
824     private static Node toNodeOrTextNode(final Node thisObj, final Object obj) {
825         if (obj instanceof Node) {
826             return (Node) obj;
827         }
828         return (Node)
829                 ((HTMLDocument) thisObj.getOwnerDocument()).createTextNode(Context.toString(obj));
830     }
831 
832     /**
833      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
834      * just before this ChildNode.
835      * @param context the context
836      * @param thisObj this object
837      * @param args the arguments
838      * @param function the function
839      */
840     protected static void before(final Context context, final Scriptable thisObj, final Object[] args,
841             final Function function) {
842         for (Object arg : args) {
843             final Node node = toNodeOrTextNode((Node) thisObj, arg);
844             ((Node) thisObj).getDomNodeOrDie().insertBefore(node.getDomNodeOrDie());
845         }
846     }
847 
848     /**
849      * Replaces this ChildNode in the children list of its parent with a set of Node or DOMString objects.
850      * @param context the context
851      * @param thisObj this object
852      * @param args the arguments
853      * @param function the function
854      */
855     protected static void replaceWith(final Context context, final Scriptable thisObj, final Object[] args,
856             final Function function) {
857         final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
858         final DomNode parentNode = thisDomNode.getParentNode();
859         final DomNode nextSibling = thisDomNode.getNextSibling();
860         boolean isFirst = true;
861         for (Object arg : args) {
862             final DomNode newNode = toNodeOrTextNode((Node) thisObj, arg).getDomNodeOrDie();
863             if (isFirst) {
864                 isFirst = false;
865                 thisDomNode.replace(newNode);
866             }
867             else {
868                 if (nextSibling != null) {
869                     nextSibling.insertBefore(newNode);
870                 }
871                 else {
872                     parentNode.appendChild(newNode);
873                 }
874             }
875         }
876     }
877 }