1
2
3
4
5
6
7
8
9
10
11
12
13
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64 @JsxClass
65 public class Node extends EventTarget {
66
67
68
69
70 @JsxConstant
71 public static final int ELEMENT_NODE = org.w3c.dom.Node.ELEMENT_NODE;
72
73
74
75
76 @JsxConstant
77 public static final int ATTRIBUTE_NODE = org.w3c.dom.Node.ATTRIBUTE_NODE;
78
79
80
81
82 @JsxConstant
83 public static final int TEXT_NODE = org.w3c.dom.Node.TEXT_NODE;
84
85
86
87
88 @JsxConstant
89 public static final int CDATA_SECTION_NODE = org.w3c.dom.Node.CDATA_SECTION_NODE;
90
91
92
93
94 @JsxConstant
95 public static final int ENTITY_REFERENCE_NODE = org.w3c.dom.Node.ENTITY_REFERENCE_NODE;
96
97
98
99
100 @JsxConstant
101 public static final int ENTITY_NODE = org.w3c.dom.Node.ENTITY_NODE;
102
103
104
105
106 @JsxConstant
107 public static final int PROCESSING_INSTRUCTION_NODE = org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE;
108
109
110
111
112 @JsxConstant
113 public static final int COMMENT_NODE = org.w3c.dom.Node.COMMENT_NODE;
114
115
116
117
118 @JsxConstant
119 public static final int DOCUMENT_NODE = org.w3c.dom.Node.DOCUMENT_NODE;
120
121
122
123
124 @JsxConstant
125 public static final int DOCUMENT_TYPE_NODE = org.w3c.dom.Node.DOCUMENT_TYPE_NODE;
126
127
128
129
130 @JsxConstant
131 public static final int DOCUMENT_FRAGMENT_NODE = org.w3c.dom.Node.DOCUMENT_FRAGMENT_NODE;
132
133
134
135
136 @JsxConstant
137 public static final int NOTATION_NODE = org.w3c.dom.Node.NOTATION_NODE;
138
139
140
141
142 @JsxConstant
143 public static final int DOCUMENT_POSITION_DISCONNECTED = org.w3c.dom.Node.DOCUMENT_POSITION_DISCONNECTED;
144
145
146
147
148 @JsxConstant
149 public static final int DOCUMENT_POSITION_PRECEDING = org.w3c.dom.Node.DOCUMENT_POSITION_PRECEDING;
150
151
152
153
154 @JsxConstant
155 public static final int DOCUMENT_POSITION_FOLLOWING = org.w3c.dom.Node.DOCUMENT_POSITION_FOLLOWING;
156
157
158
159
160 @JsxConstant
161 public static final int DOCUMENT_POSITION_CONTAINS = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINS;
162
163
164
165
166 @JsxConstant
167 public static final int DOCUMENT_POSITION_CONTAINED_BY = org.w3c.dom.Node.DOCUMENT_POSITION_CONTAINED_BY;
168
169
170
171
172 @JsxConstant
173 public static final int DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
174 = org.w3c.dom.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
175
176
177 private NodeList childNodes_;
178
179
180
181
182 @Override
183 @JsxConstructor
184 public void jsConstructor() {
185 super.jsConstructor();
186 }
187
188
189
190
191
192 @JsxGetter
193 public int getNodeType() {
194 return getDomNodeOrDie().getNodeType();
195 }
196
197
198
199
200
201 @JsxGetter
202 public String getNodeName() {
203 return getDomNodeOrDie().getNodeName();
204 }
205
206
207
208
209
210 @JsxGetter
211 public String getNodeValue() {
212 return getDomNodeOrDie().getNodeValue();
213 }
214
215
216
217
218
219 @JsxSetter
220 public void setNodeValue(final String newValue) {
221 getDomNodeOrDie().setNodeValue(newValue);
222 }
223
224
225
226
227
228
229 @JsxFunction
230 public Node appendChild(final Object childObject) {
231 if (childObject instanceof Node) {
232 final Node childNode = (Node) childObject;
233
234
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
243 final DomNode childDomNode = childNode.getDomNodeOrDie();
244
245
246 final DomNode parentNode = getDomNodeOrDie();
247
248
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
267
268
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
281
282
283
284
285
286
287
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
297
298
299
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
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
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
373
374
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
386
387
388 protected void remove() {
389 getDomNodeOrDie().remove();
390 }
391
392
393
394
395
396
397 @JsxFunction
398 public Node removeChild(final Object childObject) {
399 if (!(childObject instanceof Node)) {
400 return null;
401 }
402
403
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
415 childDomNode.remove();
416 return childObjectNode;
417 }
418
419
420
421
422
423
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
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
461 final DomNode newChildDomNode = newChild.getDomNodeOrDie();
462 final Node oldChildNode = (Node) oldChildObject;
463 final DomNode oldChildDomNode = oldChildNode.getDomNodeOrDie();
464
465
466 oldChildDomNode.replace(newChildDomNode);
467
468 return oldChildNode;
469 }
470
471 return null;
472 }
473
474
475
476
477
478
479
480
481
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
521
522
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
534
535
536
537
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
657
658
659
660
661
662
663
664
665
666 @JsxFunction
667 public boolean isSameNode(final Object other) {
668 return this == other;
669 }
670
671
672
673
674
675 @JsxFunction
676 public boolean hasChildNodes() {
677 return getDomNodeOrDie().getChildren().iterator().hasNext();
678 }
679
680
681
682
683
684
685
686 @JsxFunction
687 public String lookupPrefix(final String namespace) {
688 return null;
689 }
690
691
692
693
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
716
717
718 public final Node getParent() {
719 return getJavaScriptNode(getDomNodeOrDie().getParentNode());
720 }
721
722
723
724
725
726
727 @JsxGetter
728 public Object getParentNode() {
729 return getJavaScriptNode(getDomNodeOrDie().getParentNode());
730 }
731
732
733
734
735
736
737
738 @JsxGetter
739 public Node getNextSibling() {
740 return getJavaScriptNode(getDomNodeOrDie().getNextSibling());
741 }
742
743
744
745
746
747
748
749 @JsxGetter
750 public Node getPreviousSibling() {
751 return getJavaScriptNode(getDomNodeOrDie().getPreviousSibling());
752 }
753
754
755
756
757
758
759
760 @JsxGetter
761 public Node getFirstChild() {
762 return getJavaScriptNode(getDomNodeOrDie().getFirstChild());
763 }
764
765
766
767
768
769
770
771 @JsxGetter
772 public Node getLastChild() {
773 return getJavaScriptNode(getDomNodeOrDie().getLastChild());
774 }
775
776
777
778
779
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
790
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
803
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
819
820
821
822
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
834
835 @JsxFunction
836 public void normalize() {
837 getDomNodeOrDie().normalize();
838 }
839
840
841
842
843
844 @JsxGetter
845 public String getTextContent() {
846 return getDomNodeOrDie().getTextContent();
847 }
848
849
850
851
852
853 @JsxSetter
854 public void setTextContent(final Object value) {
855 getDomNodeOrDie().setTextContent(value == null ? null : JavaScriptEngine.toString(value));
856 }
857
858
859
860
861
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
874
875
876
877 public NamedNodeMap getAttributes() {
878 return null;
879 }
880
881
882
883
884
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
906
907
908 @JsxGetter
909 public String getBaseURI() {
910 return getDomNodeOrDie().getBaseURI();
911 }
912
913
914
915
916
917 public boolean hasAttributes() {
918 return getDomNodeOrDie().hasAttributes();
919 }
920
921
922
923
924
925 public String getPrefix() {
926 return getDomNodeOrDie().getPrefix();
927 }
928
929
930
931
932
933 public String getLocalName() {
934 return getDomNodeOrDie().getLocalName();
935 }
936
937
938
939
940
941 public String getNamespaceURI() {
942 return getDomNodeOrDie().getNamespaceURI();
943 }
944
945
946
947
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
969
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
994
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
1018
1019
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
1040
1041
1042
1043
1044
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
1065
1066
1067
1068
1069
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
1088
1089
1090
1091
1092
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
1118
1119
1120
1121
1122
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
1150
1151
1152
1153
1154
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
1166
1167
1168
1169
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 }