View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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    * https://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 org.htmlunit.javascript.host.dom;
16  
17  import java.io.Serializable;
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Objects;
23  import java.util.function.Supplier;
24  
25  import org.htmlunit.SgmlPage;
26  import org.htmlunit.corejs.javascript.Context;
27  import org.htmlunit.corejs.javascript.Function;
28  import org.htmlunit.corejs.javascript.Scriptable;
29  import org.htmlunit.html.DomDocumentFragment;
30  import org.htmlunit.html.DomElement;
31  import org.htmlunit.html.DomNode;
32  import org.htmlunit.html.HtmlElement;
33  import org.htmlunit.html.HtmlInlineFrame;
34  import org.htmlunit.javascript.HtmlUnitScriptable;
35  import org.htmlunit.javascript.JavaScriptEngine;
36  import org.htmlunit.javascript.configuration.JsxClass;
37  import org.htmlunit.javascript.configuration.JsxConstant;
38  import org.htmlunit.javascript.configuration.JsxConstructor;
39  import org.htmlunit.javascript.configuration.JsxFunction;
40  import org.htmlunit.javascript.configuration.JsxGetter;
41  import org.htmlunit.javascript.configuration.JsxSetter;
42  import org.htmlunit.javascript.host.Element;
43  import org.htmlunit.javascript.host.NamedNodeMap;
44  import org.htmlunit.javascript.host.event.EventTarget;
45  import org.htmlunit.javascript.host.html.HTMLCollection;
46  import org.htmlunit.javascript.host.html.HTMLDocument;
47  import org.htmlunit.javascript.host.html.HTMLHtmlElement;
48  
49  /**
50   * The JavaScript object {@code Node} which is the base class for all DOM
51   * objects. This will typically wrap an instance of {@link DomNode}.
52   *
53   * @author Mike Bowler
54   * @author David K. Taylor
55   * @author Barnaby Court
56   * @author Christian Sell
57   * @author George Murnock
58   * @author Chris Erskine
59   * @author Bruce Faulkner
60   * @author Ahmed Ashour
61   * @author Ronald Brill
62   * @author Frank Danek
63   */
64  @JsxClass
65  public class Node extends EventTarget {
66  
67      /**
68       * @see org.w3c.dom.Node#ELEMENT_NODE
69       */
70      @JsxConstant
71      public static final int ELEMENT_NODE = org.w3c.dom.Node.ELEMENT_NODE;
72  
73      /**
74       * @see org.w3c.dom.Node#ATTRIBUTE_NODE
75       */
76      @JsxConstant
77      public static final int ATTRIBUTE_NODE = org.w3c.dom.Node.ATTRIBUTE_NODE;
78  
79      /**
80       * @see org.w3c.dom.Node#TEXT_NODE
81       */
82      @JsxConstant
83      public static final int TEXT_NODE = org.w3c.dom.Node.TEXT_NODE;
84  
85      /**
86       * @see org.w3c.dom.Node#CDATA_SECTION_NODE
87       */
88      @JsxConstant
89      public static final int CDATA_SECTION_NODE = org.w3c.dom.Node.CDATA_SECTION_NODE;
90  
91      /**
92       * @see org.w3c.dom.Node#ENTITY_REFERENCE_NODE
93       */
94      @JsxConstant
95      public static final int ENTITY_REFERENCE_NODE = org.w3c.dom.Node.ENTITY_REFERENCE_NODE;
96  
97      /**
98       * @see org.w3c.dom.Node#ENTITY_NODE
99       */
100     @JsxConstant
101     public static final int ENTITY_NODE = org.w3c.dom.Node.ENTITY_NODE;
102 
103     /**
104      * @see org.w3c.dom.Node#PROCESSING_INSTRUCTION_NODE
105      */
106     @JsxConstant
107     public static final int PROCESSING_INSTRUCTION_NODE = org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE;
108 
109     /**
110      * @see org.w3c.dom.Node#COMMENT_NODE
111      */
112     @JsxConstant
113     public static final int COMMENT_NODE = org.w3c.dom.Node.COMMENT_NODE;
114 
115     /**
116      * @see org.w3c.dom.Node#DOCUMENT_NODE
117      */
118     @JsxConstant
119     public static final int DOCUMENT_NODE = org.w3c.dom.Node.DOCUMENT_NODE;
120 
121     /**
122      * @see org.w3c.dom.Node#DOCUMENT_TYPE_NODE
123      */
124     @JsxConstant
125     public static final int DOCUMENT_TYPE_NODE = org.w3c.dom.Node.DOCUMENT_TYPE_NODE;
126 
127     /**
128      * @see org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE
129      */
130     @JsxConstant
131     public static final int DOCUMENT_FRAGMENT_NODE = org.w3c.dom.Node.DOCUMENT_FRAGMENT_NODE;
132 
133     /**
134      * @see org.w3c.dom.Node#NOTATION_NODE
135      */
136     @JsxConstant
137     public static final int NOTATION_NODE = org.w3c.dom.Node.NOTATION_NODE;
138 
139     /**
140      * @see org.w3c.dom.Node#DOCUMENT_POSITION_DISCONNECTED
141      */
142     @JsxConstant
143     public static final int DOCUMENT_POSITION_DISCONNECTED = org.w3c.dom.Node.DOCUMENT_POSITION_DISCONNECTED;
144 
145     /**
146      * @see org.w3c.dom.Node#DOCUMENT_POSITION_PRECEDING
147      */
148     @JsxConstant
149     public static final int DOCUMENT_POSITION_PRECEDING = org.w3c.dom.Node.DOCUMENT_POSITION_PRECEDING;
150 
151     /**
152      * @see org.w3c.dom.Node#DOCUMENT_POSITION_FOLLOWING
153      */
154     @JsxConstant
155     public static final int DOCUMENT_POSITION_FOLLOWING = org.w3c.dom.Node.DOCUMENT_POSITION_FOLLOWING;
156 
157     /**
158      * @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINS
159      */
160     @JsxConstant
161     public static final int DOCUMENT_POSITION_CONTAINS = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINS;
162 
163     /**
164      * @see org.w3c.dom.Node#DOCUMENT_POSITION_CONTAINED_BY
165      */
166     @JsxConstant
167     public static final int DOCUMENT_POSITION_CONTAINED_BY = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINED_BY;
168 
169     /**
170      * @see org.w3c.dom.Node#DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
171      */
172     @JsxConstant
173     public static final int DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
174         = org.w3c.dom.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
175 
176     /** "Live" child nodes collection; has to be a member to have equality (==) working. */
177     private NodeList childNodes_;
178 
179     /**
180      * JavaScript constructor.
181      */
182     @Override
183     @JsxConstructor
184     public void jsConstructor() {
185         super.jsConstructor();
186     }
187 
188     /**
189      * Gets the JavaScript property {@code nodeType} for the current node.
190      * @return the node type
191      */
192     @JsxGetter
193     public int getNodeType() {
194         return getDomNodeOrDie().getNodeType();
195     }
196 
197     /**
198      * Gets the JavaScript property {@code nodeName} for the current node.
199      * @return the node name
200      */
201     @JsxGetter
202     public String getNodeName() {
203         return getDomNodeOrDie().getNodeName();
204     }
205 
206     /**
207      * Gets the JavaScript property {@code nodeValue} for the current node.
208      * @return the node value
209      */
210     @JsxGetter
211     public String getNodeValue() {
212         return getDomNodeOrDie().getNodeValue();
213     }
214 
215     /**
216      * Sets the JavaScript property {@code nodeValue} for the current node.
217      * @param newValue the new node value
218      */
219     @JsxSetter
220     public void setNodeValue(final String newValue) {
221         getDomNodeOrDie().setNodeValue(newValue);
222     }
223 
224     /**
225      * Adds a DOM node to the node.
226      * @param childObject the node to add to this node
227      * @return the newly added child node
228      */
229     @JsxFunction
230     public Node appendChild(final Object childObject) {
231         if (childObject instanceof Node) {
232             final Node childNode = (Node) childObject;
233 
234             // is the node allowed here?
235             if (!isNodeInsertable(childNode)) {
236                 throw JavaScriptEngine.asJavaScriptException(
237                         getWindow(),
238                         "Node cannot be inserted at the specified point in the hierarchy",
239                         DOMException.HIERARCHY_REQUEST_ERR);
240             }
241 
242             // Get XML node for the DOM node passed in
243             final DomNode childDomNode = childNode.getDomNodeOrDie();
244 
245             // Get the parent XML node that the child should be added to.
246             final DomNode parentNode = getDomNodeOrDie();
247 
248             // Append the child to the parent node
249             try {
250                 parentNode.appendChild(childDomNode);
251             }
252             catch (final org.w3c.dom.DOMException e) {
253                 throw JavaScriptEngine.asJavaScriptException(getWindow(), e.getMessage(), e.code);
254             }
255 
256             initInlineFrameIfNeeded(childDomNode);
257             for (final HtmlElement htmlElement : childDomNode.getHtmlElementDescendants()) {
258                 initInlineFrameIfNeeded(htmlElement);
259             }
260             return childNode;
261         }
262         return null;
263     }
264 
265     /**
266      * If we have added a new iframe that
267      * had no source attribute, we have to take care the
268      * 'onload' handler is triggered.
269      */
270     private static void initInlineFrameIfNeeded(final DomNode childDomNode) {
271         if (childDomNode instanceof HtmlInlineFrame) {
272             final HtmlInlineFrame frame = (HtmlInlineFrame) childDomNode;
273             if (DomElement.ATTRIBUTE_NOT_DEFINED == frame.getSrcAttribute()) {
274                 frame.loadInnerPage();
275             }
276         }
277     }
278 
279     /**
280      * Add a DOM node as a child to this node before the referenced node.
281      * If the referenced node is null, append to the end.
282      * @param context the JavaScript context
283      * @param scope the scope
284      * @param thisObj the scriptable
285      * @param args the arguments passed into the method
286      * @param function the function
287      * @return the newly added child node
288      */
289     @JsxFunction
290     public static Node insertBefore(final Context context, final Scriptable scope,
291             final Scriptable thisObj, final Object[] args, final Function function) {
292         return ((Node) thisObj).insertBeforeImpl(args);
293     }
294 
295     /**
296      * Add a DOM node as a child to this node before the referenced node.
297      * If the referenced node is null, append to the end.
298      * @param args the arguments
299      * @return the newly added child node
300      */
301     protected Node insertBeforeImpl(final Object[] args) {
302         if (args.length < 1) {
303             throw JavaScriptEngine.typeError(
304                     "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 0 present.");
305         }
306 
307         final Object newChildObject = args[0];
308         final Object refChildObject;
309         if (args.length > 1) {
310             refChildObject = args[1];
311         }
312         else {
313             refChildObject = JavaScriptEngine.UNDEFINED;
314         }
315 
316         if (newChildObject instanceof Node) {
317             final Node newChild = (Node) newChildObject;
318 
319             // is the node allowed here?
320             if (!isNodeInsertable(newChild)) {
321                 throw JavaScriptEngine.asJavaScriptException(
322                         getWindow(),
323                         "Node cannot be inserted at the specified point in the hierarchy",
324                         DOMException.HIERARCHY_REQUEST_ERR);
325             }
326 
327             final DomNode newChildNode = newChild.getDomNodeOrDie();
328             if (newChildNode instanceof DomDocumentFragment) {
329                 final DomDocumentFragment fragment = (DomDocumentFragment) newChildNode;
330                 for (final DomNode child : fragment.getChildren()) {
331                     if (!isNodeInsertable(child.getScriptableObject())) {
332                         throw JavaScriptEngine.asJavaScriptException(
333                                 getWindow(),
334                                 "Node cannot be inserted at the specified point in the hierarchy",
335                                 DOMException.HIERARCHY_REQUEST_ERR);
336                     }
337                 }
338             }
339 
340             // extract refChild
341             final DomNode refChildNode;
342             if (JavaScriptEngine.isUndefined(refChildObject)) {
343                 if (args.length == 2) {
344                     refChildNode = null;
345                 }
346                 else {
347                     throw JavaScriptEngine.typeError(
348                             "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present.");
349                 }
350             }
351             else if (refChildObject == null) {
352                 refChildNode = null;
353             }
354             else {
355                 refChildNode = ((Node) refChildObject).getDomNodeOrDie();
356             }
357 
358             final DomNode domNode = getDomNodeOrDie();
359 
360             try {
361                 domNode.insertBefore(newChildNode, refChildNode);
362             }
363             catch (final org.w3c.dom.DOMException e) {
364                 throw JavaScriptEngine.asJavaScriptException(getWindow(), e.getMessage(), DOMException.NOT_FOUND_ERR);
365             }
366             return newChild;
367         }
368         return null;
369     }
370 
371     /**
372      * Indicates if the node can be inserted.
373      * @param childObject the node
374      * @return {@code false} if it is not allowed here
375      */
376     private static boolean isNodeInsertable(final Node childObject) {
377         if (childObject instanceof HTMLHtmlElement) {
378             final DomNode domNode = childObject.getDomNodeOrDie();
379             return domNode.getPage().getDocumentElement() != domNode;
380         }
381         return true;
382     }
383 
384     /**
385      * Removes the DOM node from its parent.
386      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove">MDN documentation</a>
387      */
388     protected void remove() {
389         getDomNodeOrDie().remove();
390     }
391 
392     /**
393      * Removes a DOM node from this node.
394      * @param childObject the node to remove from this node
395      * @return the removed child node
396      */
397     @JsxFunction
398     public Node removeChild(final Object childObject) {
399         if (!(childObject instanceof Node)) {
400             return null;
401         }
402 
403         // Get XML node for the DOM node passed in
404         final Node childObjectNode = (Node) childObject;
405         final DomNode childDomNode = childObjectNode.getDomNodeOrDie();
406 
407         if (!getDomNodeOrDie().isAncestorOf(childDomNode)) {
408             throw JavaScriptEngine.asJavaScriptException(
409                     getWindow(),
410                     "Failed to execute 'removeChild' on '"
411                             + this + "': The node to be removed is not a child of this node.",
412                     DOMException.NOT_FOUND_ERR);
413         }
414         // Remove the child from the parent node
415         childDomNode.remove();
416         return childObjectNode;
417     }
418 
419     /**
420      * Replaces a child DOM node with another DOM node.
421      * @param newChildObject the node to add as a child of this node
422      * @param oldChildObject the node to remove as a child of this node
423      * @return the removed child node
424      */
425     @JsxFunction
426     public Node replaceChild(final Object newChildObject, final Object oldChildObject) {
427         if (newChildObject instanceof DocumentFragment) {
428             final DocumentFragment fragment = (DocumentFragment) newChildObject;
429             Node firstNode = null;
430 
431             final Node oldChildNode = (Node) oldChildObject;
432             final Node refChildObject = oldChildNode.getNextSibling();
433             for (final DomNode node : fragment.getDomNodeOrDie().getChildren()) {
434                 if (firstNode == null) {
435                     replaceChild(node.getScriptableObject(), oldChildObject);
436                     firstNode = node.getScriptableObject();
437                 }
438                 else {
439                     insertBeforeImpl(new Object[] {node.getScriptableObject(), refChildObject});
440                 }
441             }
442             if (firstNode == null) {
443                 removeChild(oldChildObject);
444             }
445 
446             return oldChildNode;
447         }
448 
449         if (newChildObject instanceof Node && oldChildObject instanceof Node) {
450             final Node newChild = (Node) newChildObject;
451 
452             // is the node allowed here?
453             if (!isNodeInsertable(newChild)) {
454                 throw JavaScriptEngine.asJavaScriptException(
455                         getWindow(),
456                         "Node cannot be inserted at the specified point in the hierarchy",
457                         DOMException.HIERARCHY_REQUEST_ERR);
458             }
459 
460             // Get XML nodes for the DOM nodes passed in
461             final DomNode newChildDomNode = newChild.getDomNodeOrDie();
462             final Node oldChildNode = (Node) oldChildObject;
463             final DomNode oldChildDomNode = oldChildNode.getDomNodeOrDie();
464 
465             // Replace the old child with the new child.
466             oldChildDomNode.replace(newChildDomNode);
467 
468             return oldChildNode;
469         }
470 
471         return null;
472     }
473 
474     /**
475      * Moves a given Node inside the invoking node as a direct child, before a given reference node.
476      *
477      * @param context the JavaScript context
478      * @param scope the scope
479      * @param thisObj the scriptable
480      * @param args the arguments passed into the method
481      * @param function the function
482      */
483     public static void moveBefore(final Context context, final Scriptable scope,
484             final Scriptable thisObj, final Object[] args, final Function function) {
485         if (args.length < 2) {
486             throw JavaScriptEngine.typeError(
487                     "Failed to execute 'moveBefore' on 'Element': 2 arguments required, but only 0 present.");
488         }
489 
490         final Object movedNodeObject = args[0];
491         if (!(movedNodeObject instanceof Node)) {
492             throw JavaScriptEngine.typeError(
493                     "Failed to execute 'moveBefore' on 'Element': parameter 1 is not of type 'Node'.");
494         }
495         final Object referenceNodeObject = args[1];
496         if (referenceNodeObject != null && !(referenceNodeObject instanceof Node)) {
497             throw JavaScriptEngine.typeError(
498                     "Failed to execute 'moveBefore' on 'Element': parameter 2 is not of type 'Node'.");
499         }
500 
501         final Node node = (Node) thisObj;
502         try {
503             if (referenceNodeObject == null) {
504                 node.getDomNodeOrDie().moveBefore(((Node) movedNodeObject).getDomNodeOrDie(), null);
505                 return;
506             }
507 
508             node.getDomNodeOrDie().moveBefore(
509                     ((Node) movedNodeObject).getDomNodeOrDie(), ((Node) referenceNodeObject).getDomNodeOrDie());
510         }
511         catch (final org.w3c.dom.DOMException e) {
512             throw JavaScriptEngine.asJavaScriptException(
513                     node.getWindow(),
514                     "Failed to execute 'moveChild' on '" + node + ": " + e.getMessage(),
515                     e.code);
516         }
517     }
518 
519     /**
520      * Clones this node.
521      * @param deep if {@code true}, recursively clones all descendants
522      * @return the newly cloned node
523      */
524     @JsxFunction
525     public Node cloneNode(final boolean deep) {
526         final DomNode domNode = getDomNodeOrDie();
527         final DomNode clonedNode = domNode.cloneNode(deep);
528 
529         return getJavaScriptNode(clonedNode);
530     }
531 
532     /**
533      * Check if 2 nodes are equals.
534      * For detail specifications
535      * @see <a href="https://dom.spec.whatwg.org/#concept-node-equals">concept-node-equals</a>
536      * @param other the node to compare with
537      * @return true or false
538      */
539     @JsxFunction
540     public boolean isEqualNode(final Node other) {
541         if (isSameNode(other)) {
542             return true;
543         }
544 
545         if (other == null) {
546             return false;
547         }
548 
549         if (!getClassName().equals(other.getClassName())) {
550             return false;
551         }
552 
553         if (this instanceof DocumentType) {
554             final DocumentType docType = (DocumentType) this;
555             final DocumentType otherDocType = (DocumentType) other;
556             if (!Objects.equals(docType.getName(), otherDocType.getName())
557                     || !Objects.equals(docType.getPublicId(), otherDocType.getPublicId())
558                     || !Objects.equals(docType.getSystemId(), otherDocType.getSystemId())) {
559                 return false;
560             }
561 
562         }
563         else if (this instanceof Element) {
564             final Element element = (Element) this;
565             final Element otherElement = (Element) other;
566             if (!Objects.equals(element.getNodeName(), otherElement.getNodeName())
567                     || !Objects.equals(element.getPrefix(), otherElement.getPrefix())
568                     || !Objects.equals(element.getLocalName(), otherElement.getLocalName())) {
569                 return false;
570             }
571 
572             final NamedNodeMap attributesMap = element.getAttributes();
573             final NamedNodeMap otherAttributesMap = otherElement.getAttributes();
574             if (attributesMap != null || otherAttributesMap != null) {
575                 if (attributesMap == null || otherAttributesMap == null) {
576                     return false;
577                 }
578 
579                 final int length = attributesMap.getLength();
580                 if (length != otherAttributesMap.getLength()) {
581                     return false;
582                 }
583 
584                 final Map<String, Attr> name2Attributes = new HashMap<>();
585                 for (int i = 0; i < length; i++) {
586                     final Attr attribute = (Attr) attributesMap.item(i);
587                     name2Attributes.put(attribute.getName(), attribute);
588                 }
589 
590                 for (int i = 0; i < length; i++) {
591                     final Attr otherAttribute = (Attr) otherAttributesMap.item(i);
592                     final Attr attribute = name2Attributes.get(otherAttribute.getName());
593                     if (attribute == null) {
594                         return false;
595                     }
596                     if (!attribute.isEqualNode(otherAttribute)) {
597                         return false;
598                     }
599                 }
600             }
601 
602         }
603         else if (this instanceof Attr) {
604             final Attr attr = (Attr) this;
605             final Attr otherAttr = (Attr) other;
606             if (!Objects.equals(attr.getName(), otherAttr.getName())
607                     || !Objects.equals(attr.getLocalName(), otherAttr.getLocalName())
608                     || !Objects.equals(attr.getValue(), otherAttr.getValue())) {
609                 return false;
610             }
611 
612         }
613         else if (this instanceof ProcessingInstruction) {
614             final ProcessingInstruction instruction = (ProcessingInstruction) this;
615             final ProcessingInstruction otherInstruction = (ProcessingInstruction) other;
616             if (!Objects.equals(instruction.getTarget(), otherInstruction.getTarget())
617                     || !Objects.equals(instruction.getData(), otherInstruction.getData())) {
618                 return false;
619             }
620 
621         }
622         else if (this instanceof Text || this instanceof Comment) {
623             final CharacterData data = (CharacterData) this;
624             final CharacterData otherData = (CharacterData) other;
625             if (!Objects.equals(data.getData(), otherData.getData())) {
626                 return false;
627             }
628         }
629 
630         final NodeList childNodes = getChildNodes();
631         final NodeList otherChildNodes = other.getChildNodes();
632         if (childNodes != null || otherChildNodes != null) {
633             if (childNodes == null || otherChildNodes == null) {
634                 return false;
635             }
636 
637             final int length = childNodes.getLength();
638             final int otherLength = otherChildNodes.getLength();
639             if (length != otherLength) {
640                 return false;
641             }
642 
643             for (int i = 0; i < length; i++) {
644                 final Node childNode = (Node) childNodes.item(i);
645                 final Node otherChildNode = (Node) otherChildNodes.item(i);
646                 if (!childNode.isEqualNode(otherChildNode)) {
647                     return false;
648                 }
649             }
650         }
651 
652         return true;
653     }
654 
655     /**
656      * This method provides a way to determine whether two Node references returned by
657      * the implementation reference the same object.
658      * When two Node references are references to the same object, even if through a proxy,
659      * the references may be used completely interchangeably, such that all attributes
660      * have the same values and calling the same DOM method on either reference always has exactly the same effect.
661      *
662      * @param other the node to test against
663      *
664      * @return whether this node is the same node as the given one
665      */
666     @JsxFunction
667     public boolean isSameNode(final Object other) {
668         return this == other;
669     }
670 
671     /**
672      * Returns whether this node has any children.
673      * @return boolean true if this node has any children, false otherwise
674      */
675     @JsxFunction
676     public boolean hasChildNodes() {
677         return getDomNodeOrDie().getChildren().iterator().hasNext();
678     }
679 
680     /**
681      * @param namespace string containing the namespace to look the prefix up
682      * @return a string containing the prefix for a given namespace URI,
683      *         if present, and null if not. When multiple prefixes are possible,
684      *         the first prefix is returned.
685      */
686     @JsxFunction
687     public String lookupPrefix(final String namespace) {
688         return null;
689     }
690 
691     /**
692      * Returns the child nodes of the current element.
693      * @return the child nodes of the current element
694      */
695     @JsxGetter
696     public NodeList getChildNodes() {
697         if (childNodes_ == null) {
698             final DomNode node = getDomNodeOrDie();
699             childNodes_ = new NodeList(node, false);
700             childNodes_.setElementsSupplier(
701                     (Supplier<List<DomNode>> & Serializable)
702                     () -> {
703                         final List<DomNode> response = new ArrayList<>();
704                         for (final DomNode child : node.getChildren()) {
705                             response.add(child);
706                         }
707 
708                         return response;
709                     });
710         }
711         return childNodes_;
712     }
713 
714     /**
715      * Returns this node's parent node.
716      * @return this node's parent node
717      */
718     public final Node getParent() {
719         return getJavaScriptNode(getDomNodeOrDie().getParentNode());
720     }
721 
722     /**
723      * Gets the JavaScript property {@code parentNode} for the node that
724      * contains the current node.
725      * @return the parent node
726      */
727     @JsxGetter
728     public Object getParentNode() {
729         return getJavaScriptNode(getDomNodeOrDie().getParentNode());
730     }
731 
732     /**
733      * Gets the JavaScript property {@code nextSibling} for the node that
734      * contains the current node.
735      * @return the next sibling node or null if the current node has
736      *         no next sibling.
737      */
738     @JsxGetter
739     public Node getNextSibling() {
740         return getJavaScriptNode(getDomNodeOrDie().getNextSibling());
741     }
742 
743     /**
744      * Gets the JavaScript property {@code previousSibling} for the node that
745      * contains the current node.
746      * @return the previous sibling node or null if the current node has
747      *         no previous sibling.
748      */
749     @JsxGetter
750     public Node getPreviousSibling() {
751         return getJavaScriptNode(getDomNodeOrDie().getPreviousSibling());
752     }
753 
754     /**
755      * Gets the JavaScript property {@code firstChild} for the node that
756      * contains the current node.
757      * @return the first child node or null if the current node has
758      *         no children.
759      */
760     @JsxGetter
761     public Node getFirstChild() {
762         return getJavaScriptNode(getDomNodeOrDie().getFirstChild());
763     }
764 
765     /**
766      * Gets the JavaScript property {@code lastChild} for the node that
767      * contains the current node.
768      * @return the last child node or null if the current node has
769      *         no children.
770      */
771     @JsxGetter
772     public Node getLastChild() {
773         return getJavaScriptNode(getDomNodeOrDie().getLastChild());
774     }
775 
776     /**
777      * Gets the JavaScript node for a given DomNode.
778      * @param domNode the DomNode
779      * @return the JavaScript node or null if the DomNode was null
780      */
781     protected Node getJavaScriptNode(final DomNode domNode) {
782         if (domNode == null) {
783             return null;
784         }
785         return (Node) getScriptableFor(domNode);
786     }
787 
788     /**
789      * Returns the owner document.
790      * @return the document
791      */
792     @JsxGetter
793     public HtmlUnitScriptable getOwnerDocument() {
794         final Object document = getDomNodeOrDie().getOwnerDocument();
795         if (document != null) {
796             return ((SgmlPage) document).getScriptableObject();
797         }
798         return null;
799     }
800 
801     /**
802      * Returns the owner document.
803      * @return the document
804      */
805     @JsxFunction
806     public Node getRootNode() {
807         Node parent = this;
808         while (parent != null) {
809             if (parent instanceof Document || parent instanceof DocumentFragment) {
810                 return parent;
811             }
812             parent = parent.getParent();
813         }
814         return this;
815     }
816 
817     /**
818      * Compares the positions of this node and the provided node within the document.
819      * @param node node object that specifies the node to check
820      * @return how the node is positioned relatively to the reference node.
821      * @see <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM level 3</a>
822      * @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
823      */
824     @JsxFunction
825     public int compareDocumentPosition(final Object node) {
826         if (!(node instanceof Node)) {
827             throw JavaScriptEngine.typeError("Could not convert JavaScript argument arg 0");
828         }
829         return getDomNodeOrDie().compareDocumentPosition(((Node) node).getDomNodeOrDie());
830     }
831 
832     /**
833      * Merges adjacent TextNode objects to produce a normalized document object model.
834      */
835     @JsxFunction
836     public void normalize() {
837         getDomNodeOrDie().normalize();
838     }
839 
840     /**
841      * Gets the textContent attribute.
842      * @return the contents of this node as text
843      */
844     @JsxGetter
845     public String getTextContent() {
846         return getDomNodeOrDie().getTextContent();
847     }
848 
849     /**
850      * Replace all children elements of this element with the supplied value.
851      * @param value - the new value for the contents of this node
852      */
853     @JsxSetter
854     public void setTextContent(final Object value) {
855         getDomNodeOrDie().setTextContent(value == null ? null : JavaScriptEngine.toString(value));
856     }
857 
858     /**
859      * Gets the JavaScript property {@code parentElement}.
860      * @return the parent element
861      * @see #getParentNode()
862      */
863     @JsxGetter
864     public Element getParentElement() {
865         final Node parent = getParent();
866         if (!(parent instanceof Element)) {
867             return null;
868         }
869         return (Element) parent;
870     }
871 
872     /**
873      * Returns the attributes of this XML element.
874      * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/Node.attributes">Gecko DOM Reference</a>
875      * @return the attributes of this XML element
876      */
877     public NamedNodeMap getAttributes() {
878         return null;
879     }
880 
881     /**
882      * Checks whether the given element is contained within this object.
883      * @param element element object that specifies the element to check
884      * @return true if the element is contained within this object
885      */
886     @JsxFunction
887     public boolean contains(final Object element) {
888         if (element == null || JavaScriptEngine.isUndefined(element)) {
889             return false;
890         }
891 
892         if (!(element instanceof Node)) {
893             throw JavaScriptEngine.reportRuntimeError("Could not convert JavaScript argument arg 0");
894         }
895 
896         for (Node parent = (Node) element; parent != null; parent = parent.getParentElement()) {
897             if (this == parent) {
898                 return true;
899             }
900         }
901         return false;
902     }
903 
904     /**
905      * Returns the Base URI as a string.
906      * @return the Base URI as a string
907      */
908     @JsxGetter
909     public String getBaseURI() {
910         return getDomNodeOrDie().getBaseURI();
911     }
912 
913     /**
914      * Returns true when the current element has any attributes or not.
915      * @return true if an attribute is specified on this element
916      */
917     public boolean hasAttributes() {
918         return getDomNodeOrDie().hasAttributes();
919     }
920 
921     /**
922      * Returns the namespace prefix.
923      * @return the namespace prefix
924      */
925     public String getPrefix() {
926         return getDomNodeOrDie().getPrefix();
927     }
928 
929     /**
930      * Returns the local name of this attribute.
931      * @return the local name of this attribute
932      */
933     public String getLocalName() {
934         return getDomNodeOrDie().getLocalName();
935     }
936 
937     /**
938      * Returns the URI that identifies an XML namespace.
939      * @return the URI that identifies an XML namespace
940      */
941     public String getNamespaceURI() {
942         return getDomNodeOrDie().getNamespaceURI();
943     }
944 
945     /**
946      * Returns the current number of child elements.
947      * @return the child element count
948      */
949     protected int getChildElementCount() {
950         final DomNode domNode = getDomNodeOrDie();
951         if (domNode instanceof DomElement) {
952             return ((DomElement) domNode).getChildElementCount();
953         }
954 
955         int counter = 0;
956         for (final DomNode child : getDomNodeOrDie().getChildren()) {
957             if (child != null) {
958                 final HtmlUnitScriptable scriptable = child.getScriptableObject();
959                 if (scriptable instanceof Element) {
960                     counter++;
961                 }
962             }
963         }
964         return counter;
965     }
966 
967     /**
968      * Returns the first element child.
969      * @return the first element child
970      */
971     protected Element getFirstElementChild() {
972         final DomNode domNode = getDomNodeOrDie();
973         if (domNode instanceof DomElement) {
974             final DomElement child = ((DomElement) domNode).getFirstElementChild();
975             if (child != null) {
976                 return child.getScriptableObject();
977             }
978             return null;
979         }
980 
981         for (final DomNode child : domNode.getChildren()) {
982             if (child != null) {
983                 final HtmlUnitScriptable scriptable = child.getScriptableObject();
984                 if (scriptable instanceof Element) {
985                     return (Element) scriptable;
986                 }
987             }
988         }
989         return null;
990     }
991 
992     /**
993      * Returns the last element child.
994      * @return the last element child
995      */
996     protected Element getLastElementChild() {
997         final DomNode domNode = getDomNodeOrDie();
998         if (domNode instanceof DomElement) {
999             final DomElement child = ((DomElement) getDomNodeOrDie()).getLastElementChild();
1000             if (child != null) {
1001                 return child.getScriptableObject();
1002             }
1003             return null;
1004         }
1005 
1006         Element result = null;
1007         for (final DomNode child : domNode.getChildren()) {
1008             final HtmlUnitScriptable scriptable = child.getScriptableObject();
1009             if (scriptable instanceof Element) {
1010                 result = (Element) scriptable;
1011             }
1012         }
1013         return result;
1014     }
1015 
1016     /**
1017      * Gets the children of the current node.
1018      * @see <a href="http://msdn.microsoft.com/en-us/library/ms537446.aspx">MSDN documentation</a>
1019      * @return the child at the given position
1020      */
1021     protected HTMLCollection getChildren() {
1022         final DomNode node = getDomNodeOrDie();
1023         final HTMLCollection childrenColl = new HTMLCollection(node, false);
1024         childrenColl.setElementsSupplier(
1025                 (Supplier<List<DomNode>> & Serializable)
1026                 () -> {
1027                     final List<DomNode> children = new ArrayList<>();
1028                     for (final DomNode domNode : node.getChildNodes()) {
1029                         if (domNode instanceof DomElement) {
1030                             children.add(domNode);
1031                         }
1032                     }
1033                     return children;
1034                 });
1035         return childrenColl;
1036     }
1037 
1038     /**
1039      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
1040      * just after this ChildNode.
1041      * @param context the context
1042      * @param thisObj this object
1043      * @param args the arguments
1044      * @param function the function
1045      */
1046     protected static void after(final Context context, final Scriptable thisObj, final Object[] args,
1047             final Function function) {
1048         final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
1049         final DomNode parentNode = thisDomNode.getParentNode();
1050         final DomNode nextSibling = thisDomNode.getNextSibling();
1051         for (final Object arg : args) {
1052             final Node node = toNodeOrTextNode((Node) thisObj, arg);
1053             final DomNode newNode = node.getDomNodeOrDie();
1054             if (nextSibling == null) {
1055                 parentNode.appendChild(newNode);
1056             }
1057             else {
1058                 nextSibling.insertBefore(newNode);
1059             }
1060         }
1061     }
1062 
1063     /**
1064      * Inserts a set of Node objects or string objects after the last child of the Element.
1065      * String objects are inserted as equivalent Text nodes.
1066      * @param context the context
1067      * @param thisObj this object
1068      * @param args the arguments
1069      * @param function the function
1070      */
1071     protected static void append(final Context context, final Scriptable thisObj, final Object[] args,
1072             final Function function) {
1073         if (!(thisObj instanceof Node)) {
1074             throw JavaScriptEngine.typeError("Illegal invocation");
1075         }
1076 
1077         final Node thisNode = (Node) thisObj;
1078         final DomNode thisDomNode = thisNode.getDomNodeOrDie();
1079 
1080         for (final Object arg : args) {
1081             final Node node = toNodeOrTextNode(thisNode, arg);
1082             thisDomNode.appendChild(node.getDomNodeOrDie());
1083         }
1084     }
1085 
1086     /**
1087      * Inserts a set of Node objects or string objects before the first child of the Element.
1088      * String objects are inserted as equivalent Text nodes.
1089      * @param context the context
1090      * @param thisObj this object
1091      * @param args the arguments
1092      * @param function the function
1093      */
1094     protected static void prepend(final Context context, final Scriptable thisObj, final Object[] args,
1095             final Function function) {
1096         if (!(thisObj instanceof Node)) {
1097             throw JavaScriptEngine.typeError("Illegal invocation");
1098         }
1099 
1100         final Node thisNode = (Node) thisObj;
1101         final DomNode thisDomNode = thisNode.getDomNodeOrDie();
1102         final DomNode firstChild = thisDomNode.getFirstChild();
1103 
1104         for (final Object arg : args) {
1105             final Node node = toNodeOrTextNode(thisNode, arg);
1106             final DomNode newNode = node.getDomNodeOrDie();
1107             if (firstChild == null) {
1108                 thisDomNode.appendChild(newNode);
1109             }
1110             else {
1111                 firstChild.insertBefore(newNode);
1112             }
1113         }
1114     }
1115 
1116     /**
1117      * Replaces the existing children of a Node with a specified new set of children.
1118      * These can be string or Node objects.
1119      * @param context the context
1120      * @param thisObj this object
1121      * @param args the arguments
1122      * @param function the function
1123      */
1124     protected static void replaceChildren(final Context context, final Scriptable thisObj, final Object[] args,
1125             final Function function) {
1126         if (!(thisObj instanceof Node)) {
1127             throw JavaScriptEngine.typeError("Illegal invocation");
1128         }
1129 
1130         final Node thisNode = (Node) thisObj;
1131         final DomNode thisDomNode = thisNode.getDomNodeOrDie();
1132         thisDomNode.removeAllChildren();
1133 
1134         for (final Object arg : args) {
1135             final Node node = toNodeOrTextNode(thisNode, arg);
1136             thisDomNode.appendChild(node.getDomNodeOrDie());
1137         }
1138     }
1139 
1140     private static Node toNodeOrTextNode(final Node thisObj, final Object obj) {
1141         if (obj instanceof Node) {
1142             return (Node) obj;
1143         }
1144         return (Node)
1145                 ((HTMLDocument) thisObj.getOwnerDocument()).createTextNode(JavaScriptEngine.toString(obj));
1146     }
1147 
1148     /**
1149      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
1150      * just before this ChildNode.
1151      * @param context the context
1152      * @param thisObj this object
1153      * @param args the arguments
1154      * @param function the function
1155      */
1156     protected static void before(final Context context, final Scriptable thisObj, final Object[] args,
1157             final Function function) {
1158         for (final Object arg : args) {
1159             final Node node = toNodeOrTextNode((Node) thisObj, arg);
1160             ((Node) thisObj).getDomNodeOrDie().insertBefore(node.getDomNodeOrDie());
1161         }
1162     }
1163 
1164     /**
1165      * Replaces this ChildNode in the children list of its parent with a set of Node or DOMString objects.
1166      * @param context the context
1167      * @param thisObj this object
1168      * @param args the arguments
1169      * @param function the function
1170      */
1171     protected static void replaceWith(final Context context, final Scriptable thisObj, final Object[] args,
1172             final Function function) {
1173         final DomNode thisDomNode = ((Node) thisObj).getDomNodeOrDie();
1174         final DomNode parentNode = thisDomNode.getParentNode();
1175 
1176         if (args.length == 0) {
1177             parentNode.removeChild(thisDomNode);
1178             return;
1179         }
1180 
1181         final DomNode nextSibling = thisDomNode.getNextSibling();
1182         boolean isFirst = true;
1183         for (final Object arg : args) {
1184             final DomNode newNode = toNodeOrTextNode((Node) thisObj, arg).getDomNodeOrDie();
1185             if (isFirst) {
1186                 isFirst = false;
1187                 thisDomNode.replace(newNode);
1188             }
1189             else {
1190                 if (nextSibling == null) {
1191                     parentNode.appendChild(newNode);
1192                 }
1193                 else {
1194                     nextSibling.insertBefore(newNode);
1195                 }
1196             }
1197         }
1198     }
1199 }